// astroparse — annotated reading view: main text + marginalia

const MODES = window.ASTROPARSE_MODES;
const modeById = Object.fromEntries(MODES.map((m) => [m.id, m]));

/* ---------- citation popover ---------- */

function ScoreBar({ label, value, max }) {
  const pct = Math.max(0, Math.min(1, value / max));
  return (
    <div className="ap-score">
      <span className="ap-score-label">{label}</span>
      <span className="ap-score-track"><span className="ap-score-fill" style={{ width: (pct * 100).toFixed(0) + '%' }}></span></span>
      <span className="ap-score-val">{value}</span>
    </div>
  );
}

function CitePopover({ lit, pos, showScores, onClose }) {
  const ref = React.useRef(null);
  React.useLayoutEffect(() => {
    const el = ref.current; if (!el) return;
    const r = el.getBoundingClientRect();
    let x = pos.x, y = pos.y + 10;
    if (x + r.width > window.innerWidth - 16) x = window.innerWidth - r.width - 16;
    if (y + r.height > window.innerHeight - 16) y = pos.y - r.height - 10;
    el.style.left = Math.max(8, x) + 'px';
    el.style.top = Math.max(8, y) + 'px';
    el.style.opacity = 1;
  }, [lit, pos]);
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  return (
    <div>
      <div className="ap-pop-backdrop" onClick={onClose}></div>
      <div className="ap-pop" ref={ref} style={{ opacity: 0 }}>
        <div className="ap-pop-rank">retrieved &middot; rank {lit.scores.rank}</div>
        <div className="ap-pop-title">{lit.title}</div>
        <div className="ap-pop-authors">{lit.authors} ({lit.year}) &middot; {lit.journal}</div>
        <div className="ap-pop-abstract">{lit.abstract}</div>
        {showScores && (
          <div className="ap-pop-scores">
            <ScoreBar label="specter" value={lit.scores.specter} max={1} />
            <ScoreBar label="bm25" value={lit.scores.bm25} max={20} />
          </div>
        )}
        <div className="ap-pop-links">
          <a href={'https://ui.adsabs.harvard.edu/abs/' + lit.bibcode + '/abstract'} target="_blank" rel="noopener">ADS &#8599;</a>
          <a href={'https://arxiv.org/abs/' + lit.arxiv} target="_blank" rel="noopener">arXiv &#8599;</a>
        </div>
        <button className="ap-pop-close" onClick={onClose} title="close">&times;</button>
      </div>
    </div>
  );
}

/* ---------- margin note ---------- */

function MarginNote({ para, mode, entry, generating, hovered, onHover, onSetMode, onRegenerate, onCite, top, noteRef }) {
  const m = modeById[mode];
  const lit = (window.ASTROPARSE_DATA.litByPara[para.id] || []).map((id) => window.ASTROPARSE_DATA.litPapers[id]).filter(Boolean);
  return (
    <aside
      ref={noteRef}
      className={'ap-note' + (hovered ? ' is-hovered' : '')}
      style={{ top: top, '--mode-c': 'var(--mc-' + mode + ')' }}
      onMouseEnter={() => onHover(para.id)}
      onMouseLeave={() => onHover(null)}
      data-comment-anchor={'note-' + para.id}
    >
      <div className="ap-note-head">
        <span className="ap-note-mode">{m.label}</span>
        {entry && entry.source === 'llm' && !generating && <span className="ap-note-gen" title="generated by the annotator">&#10022;</span>}
        {entry && !generating && (
          <button className="ap-note-regen" title="regenerate this note" onClick={() => onRegenerate(para.id, mode)}>&#8635;</button>
        )}
      </div>
      {generating ? (
        <div className="ap-note-loading">consulting the literature<span className="ap-ellipsis"></span></div>
      ) : entry && entry.error ? (
        <div className="ap-note-error">
          {entry.message ? entry.message + ' \u2014 ' : 'the annotator did not answer. '}
          <button className="ap-textbtn" onClick={() => onRegenerate(para.id, mode)}>try again</button>
        </div>
      ) : entry ? (
        <div className="ap-note-text">{entry.text}</div>
      ) : (
        <div className="ap-note-loading">awaiting annotation&hellip;</div>
      )}
      {lit.length > 0 && (
        <div className="ap-note-cites">
          {lit.map((l, i) => (
            <button key={l.id} className="ap-cite" onClick={(e) => onCite(l, e)}>
              <span className="ap-cite-n">{i + 1}</span> {l.short}
            </button>
          ))}
        </div>
      )}
      <div className="ap-note-modes" role="group" aria-label="commentary mode">
        {MODES.map((mm) => (
          <button
            key={mm.id}
            className={'ap-mode-dot' + (mm.id === mode ? ' is-active' : '')}
            style={{ '--mode-c': 'var(--mc-' + mm.id + ')' }}
            title={mm.label}
            onClick={() => onSetMode(para.id, mm.id)}
          >{mm.glyph}</button>
        ))}
      </div>
    </aside>
  );
}

/* ---------- reader ---------- */

function Reader({ paper, commentary, noteModes, generating, onSetMode, onRegenerate, tweaks, toolbar }) {
  const containerRef = React.useRef(null);
  const marginRef = React.useRef(null);
  const paraRefs = React.useRef({});
  const noteRefs = React.useRef({});
  const [tops, setTops] = React.useState({});
  const [spacers, setSpacers] = React.useState({});
  const spacersRef = React.useRef({});
  const [marginH, setMarginH] = React.useState(0);
  const [hoverId, setHoverId] = React.useState(null);
  const [pop, setPop] = React.useState(null); // {lit, pos}

  const paras = paper.paragraphs;

  const recompute = React.useCallback(() => {
    const c = containerRef.current; if (!c) return;
    const cTop = c.getBoundingClientRect().top;
    const narrow = window.innerWidth <= 900;
    const applied = spacersRef.current;
    const NOTE_GAP = 26;

    // reconstruct each paragraph's natural top (as if no spacers were applied)
    const naturalTop = {};
    let cum = 0;
    paras.forEach((p) => {
      const pe = paraRefs.current[p.id]; if (!pe) return;
      naturalTop[p.id] = pe.getBoundingClientRect().top - cTop - cum;
      cum += applied[p.id] || 0;
    });

    // pin every note to its paragraph's top; pad the text column below any
    // paragraph shorter than its note so the next pair stays level too
    const nextTops = {}; const nextSpacers = {};
    let cum2 = 0; let lastBottom = 0;
    paras.forEach((p, i) => {
      const ne = noteRefs.current[p.id];
      if (naturalTop[p.id] == null || !ne) return;
      const top = naturalTop[p.id] + cum2;
      nextTops[p.id] = top;
      lastBottom = top + ne.offsetHeight + NOTE_GAP;
      const nxt = paras[i + 1];
      if (!narrow && nxt && naturalTop[nxt.id] != null) {
        const delta = Math.max(0, Math.ceil(lastBottom - (naturalTop[nxt.id] + cum2)));
        if (delta > 0) nextSpacers[p.id] = delta;
        cum2 += delta;
      }
    });

    setTops((old) => {
      const same = paras.every((p) => Math.abs((old[p.id] || 0) - (nextTops[p.id] || 0)) < 1);
      return same ? old : nextTops;
    });
    const sameSp = paras.every((p) => Math.abs((applied[p.id] || 0) - (nextSpacers[p.id] || 0)) < 1);
    if (!sameSp) { spacersRef.current = nextSpacers; setSpacers(nextSpacers); }
    setMarginH(lastBottom);
  }, [paras]);

  React.useLayoutEffect(() => { recompute(); });
  React.useEffect(() => {
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(recompute);
    window.addEventListener('resize', recompute);
    return () => window.removeEventListener('resize', recompute);
  }, [recompute]);

  const showCite = (lit, e) => {
    const r = e.currentTarget.getBoundingClientRect();
    setPop({ lit, pos: { x: r.left, y: r.bottom } });
  };

  // group paragraphs by section for headings
  const rows = [];
  let lastSection = null;
  paras.forEach((p) => {
    if (p.section !== lastSection) { rows.push({ type: 'heading', section: p.section, key: 'h-' + p.id }); lastSection = p.section; }
    rows.push({ type: 'para', para: p, key: p.id });
  });

  return (
    <div className="ap-screen ap-reader" data-screen-label="Reader">
      {toolbar}
      <div className={'ap-folio' + (tweaks.dropCaps ? ' has-dropcaps' : '') + (tweaks.justify ? ' is-justified' : '')}>
        <header className="ap-paper-head">
          <div className="ap-paper-title">{paper.title}</div>
          <div className="ap-paper-authors">{paper.authors}</div>
          <div className="ap-paper-meta">arXiv:{paper.arxivId} &middot; {paper.pages} pp &middot; {paper.venue}</div>
        </header>
        <div className="ap-legend">
          {MODES.map((m) => (
            <button
              key={m.id} className="ap-legend-item" style={{ '--mode-c': 'var(--mc-' + m.id + ')' }}
              title={'set every note to ' + m.label}
              onClick={() => paras.forEach((p) => onSetMode(p.id, m.id))}
            >
              <span className="ap-legend-dot"></span>{m.label}
            </button>
          ))}
        </div>
        <div className={'ap-body' + (tweaks.notesSide === 'left' ? ' notes-left' : '')} ref={containerRef}>
          <article className="ap-text">
            {rows.map((row) =>
              row.type === 'heading' ? (
                <h2 className="ap-section" key={row.key}>{row.section}</h2>
              ) : (
                <p
                  key={row.key}
                  ref={(el) => (paraRefs.current[row.para.id] = el)}
                  className={'ap-para' + (row.para.firstOfSection ? ' is-first' : '') + (hoverId === row.para.id ? ' is-hovered' : '')}
                  style={{
                    '--mode-c': 'var(--mc-' + (noteModes[row.para.id] || 'literature') + ')',
                    marginBottom: spacers[row.para.id] ? 'calc(1.15em + ' + spacers[row.para.id] + 'px)' : undefined,
                  }}
                  onMouseEnter={() => setHoverId(row.para.id)}
                  onMouseLeave={() => setHoverId(null)}
                  data-comment-anchor={'para-' + row.para.id}
                >
                  <span className="ap-pilcrow">&para;</span>{row.para.text}
                </p>
              )
            )}
          </article>
          <div className="ap-margin" ref={marginRef} style={{ minHeight: marginH }}>
            {paras.map((p) => {
              const mode = noteModes[p.id] || 'literature';
              const key = p.id + ':' + mode;
              return (
                <MarginNote
                  key={p.id}
                  para={p}
                  mode={mode}
                  entry={commentary[key]}
                  generating={generating[key]}
                  hovered={hoverId === p.id}
                  onHover={setHoverId}
                  onSetMode={onSetMode}
                  onRegenerate={onRegenerate}
                  onCite={showCite}
                  top={tops[p.id] || 0}
                  noteRef={(el) => (noteRefs.current[p.id] = el)}
                />
              );
            })}
          </div>
        </div>
        <footer className="ap-folio-foot">
          annotations are machine-generated against retrieved literature &mdash; verify before citing
        </footer>
      </div>
      {pop && <CitePopover lit={pop.lit} pos={pop.pos} showScores={tweaks.showScores} onClose={() => setPop(null)} />}
    </div>
  );
}

Object.assign(window, { Reader });
