// viewer.jsx — document viewer: doc bar, scroll-spy TOC, Document/Diagrams/
// Metadata/Versions tabs, rendered markdown body, metadata board, context pane.

const { useState, useEffect, useRef, useMemo, useCallback } = React;

function safeId(id) { return String(id).replace(/[^A-Za-z0-9._-]/g, "_"); }

// scroll-spy: highlight the heading currently in view
function useScrollSpy(headings, ready) {
  const [active, setActive] = useState(null);
  useEffect(() => {
    if (!ready || !headings || !headings.length) return;
    const els = headings.map((h) => document.getElementById(h.id)).filter(Boolean);
    if (!els.length) return;
    const obs = new IntersectionObserver((entries) => {
      const visible = entries.filter((e) => e.isIntersecting).sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
      if (visible[0]) setActive(visible[0].target.id);
    }, { rootMargin: "-72px 0px -65% 0px" });
    els.forEach((el) => obs.observe(el));
    return () => obs.disconnect();
  }, [headings, ready]);
  return active;
}

function Toc({ headings, active, onJump }) {
  if (!headings || !headings.length) return null;
  return (
    <nav aria-label="On this page">
      <div className="toc__label">On this page</div>
      <ul className="toc__list">
        {headings.map((h, i) => (
          <li key={i}>
            <a href={"#" + h.id} className={cx("d" + h.depth, active === h.id && "active")}
               onClick={(e) => { e.preventDefault(); onJump(h.id); }}>{h.text}</a>
          </li>
        ))}
      </ul>
    </nav>
  );
}

function MetadataBoard({ doc, onOpenRef }) {
  const rel = doc.relationships || {};
  const field = (k, v, mono) => v ? <div className="metafield" key={k}><span className="metafield__k">{k}</span><span className={cx("metafield__v", mono && "mono")}>{v}</span></div> : null;
  return (
    <React.Fragment>
      {!doc.metadataComplete && <div className="state" style={{ padding: "var(--uk-space-16)", flexDirection: "row", justifyContent: "flex-start", gap: "var(--uk-space-8)" }} role="note"><I.Info /><span className="state__msg" style={{ textAlign: "left" }}>This document has partial frontmatter; some fields below are derived from its content.</span></div>}
      <div className="metaboard">
        {field("UID", doc.id, true)}
        {field("Version", doc.version, true)}
        <div className="metafield"><span className="metafield__k">Status</span><span className="metafield__v"><StatusChip stage={doc.statusStage} status={doc.status} /></span></div>
        {field("Owner", doc.owner)}
        {field("Classification", doc.classification)}
        {field("Last updated", fmtDate(doc.lastUpdated), true)}
        {doc.createdAt && field("Created", fmtDate(doc.createdAt), true)}
        {field("Reading time", doc.readingMinutes + " min · " + doc.wordCount.toLocaleString() + " words", true)}
        {doc.domains && doc.domains.length > 0 && (
          <div className="metafield metafield--full"><span className="metafield__k">Domains</span>
            <span className="metafield__v" style={{ display: "flex", flexWrap: "wrap", gap: "var(--uk-space-6)", marginTop: "4px" }}>
              {doc.domains.map((d, i) => <span className="chip" key={i} title={d.role}>{d.label} <span className="mono muted">{d.code}</span></span>)}
            </span>
          </div>
        )}
        {doc.aliases && doc.aliases.length > 0 && (
          <div className="metafield metafield--full"><span className="metafield__k">Aliases</span>
            <span className="metafield__v mono" style={{ marginTop: "2px" }}>{doc.aliases.join(" · ")}</span></div>
        )}
        {field("Source", doc.sourcePath, true)}
      </div>

      {["derivedFrom", "relatedSpecs", "referencedBy"].map((key) => {
        const list = rel[key] || []; if (!list.length) return null;
        const label = key === "derivedFrom" ? "Derived from" : key === "relatedSpecs" ? "Related documents" : "Referenced by";
        return (
          <div className="ctxcard" key={key} style={{ marginTop: "var(--uk-space-24)" }}>
            <h4 className="ctxcard__label">{label} ({list.length})</h4>
            <div className="reflist">
              {list.map((r, i) => (
                <a key={i} href="#" onClick={(e) => { e.preventDefault(); (r.title || r.collection) && onOpenRef(r); }} title={r.id}>
                  {(r.title || r.collection) ? collectionIcon(r.collection) : <I.External />}
                  <span style={{ flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{r.title || r.id}</span>
                  {!(r.title || r.collection) && <span className="mono muted" style={{ fontSize: "10px" }}>external</span>}
                </a>
              ))}
            </div>
          </div>
        );
      })}
    </React.Fragment>
  );
}

function Viewer({ docId, index, onOpenDoc, onNavigate }) {
  const [doc, setDoc] = useState(null);
  const [status, setStatus] = useState("loading"); // loading | ready | error
  const [tab, setTab] = useState("document");
  const bodyRef = useRef(null);
  const catalogEntry = useMemo(() => (index.documents || []).find((d) => d.id === docId), [index, docId]);

  const load = useCallback(() => {
    setStatus("loading"); setDoc(null); setTab("document");
    fetch("content/docs/" + encodeURIComponent(safeId(docId)) + ".json")
      .then((r) => { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
      .then((d) => { setDoc(d); setStatus("ready"); })
      .catch(() => setStatus("error"));
  }, [docId]);
  useEffect(() => { load(); }, [load]);

  // render mermaid when body present + on the document tab
  useEffect(() => {
    if (status === "ready" && tab === "document" && bodyRef.current && doc && doc.hasMermaid) {
      const id = requestAnimationFrame(() => renderMermaidIn(bodyRef.current));
      return () => cancelAnimationFrame(id);
    }
  }, [status, tab, doc]);

  // Intercept in-body "#section" anchors (heading links + internal links). Without
  // this, clicking them rewrites location.hash to a bare "#id", which the hash router
  // reads as the home route and navigates away. We scroll in place instead.
  useEffect(() => {
    const el = bodyRef.current;
    if (status !== "ready" || tab !== "document" || !el) return;
    const onClick = (ev) => {
      const a = ev.target.closest && ev.target.closest('a[href^="#"]');
      if (!a || !el.contains(a)) return;
      const href = a.getAttribute("href") || "";
      if (href.startsWith("#/")) return; // a real route link — let the router handle it
      const target = document.getElementById(decodeURIComponent(href.slice(1)));
      if (target) { ev.preventDefault(); target.scrollIntoView(); }
    };
    el.addEventListener("click", onClick);
    return () => el.removeEventListener("click", onClick);
  }, [status, tab, doc]);

  // reset scroll to top of main on doc change
  useEffect(() => { const m = document.querySelector(".app__main"); if (m) m.scrollTo({ top: 0 }); }, [docId]);

  const active = useScrollSpy(doc && doc.headings, status === "ready" && tab === "document");
  const jump = useCallback((id) => { setTab("document"); requestAnimationFrame(() => { const el = document.getElementById(id); if (el) el.scrollIntoView(); }); }, []);

  // prev / next within collection (catalog order)
  const siblings = useMemo(() => (index.documents || []).filter((d) => d.collection === (catalogEntry && catalogEntry.collection)), [index, catalogEntry]);
  const idx = siblings.findIndex((d) => d.id === docId);
  const prev = idx > 0 ? siblings[idx - 1] : null;
  const next = idx >= 0 && idx < siblings.length - 1 ? siblings[idx + 1] : null;

  if (status === "loading") {
    return <div className="viewer__main"><div className="skel" style={{ height: "32px", width: "60%", marginBottom: "16px" }} /><div className="skel" style={{ height: "16px", width: "40%", marginBottom: "32px" }} /><div className="skel" style={{ height: "300px" }} /></div>;
  }
  if (status === "error") {
    return <div className="viewer__main"><ErrorState title="Couldn’t load this document" msg={"No payload for " + docId + ". The content index may be out of date — rebuild with npm run build:content."} onRetry={load} /></div>;
  }

  const copyLink = () => { navigator.clipboard && navigator.clipboard.writeText(location.href); };
  const collLabel = (index.collections || []).find((c) => c.id === doc.collection);

  return (
    <div className="viewer">
      <aside className="viewer__toc">
        <Toc headings={doc.headings} active={active} onJump={jump} />
      </aside>

      <main className="viewer__main">
        <div className="docbar">
          <div className="docbar__eyebrow">{(collLabel ? collLabel.singular : doc.collection)} · {doc.id}</div>
          <h1 className="docbar__title">{doc.title}</h1>
          <div className="docbar__meta">
            <StatusChip stage={doc.statusStage} status={doc.status} />
            {doc.version && <React.Fragment><span className="dot">·</span><span className="mono muted">{doc.version}</span></React.Fragment>}
            {doc.owner && <React.Fragment><span className="dot">·</span><span className="muted" style={{ fontSize: "var(--uk-type-body-small)" }}>{doc.owner}</span></React.Fragment>}
            <span className="dot">·</span><span className="mono muted">{doc.readingMinutes} min</span>
            <span className="dot">·</span><span className="mono muted">updated {fmtDate(doc.lastUpdated)}</span>
          </div>
          <div className="docbar__actions">
            <button className="btn btn--ghost btn--sm" onClick={copyLink}><I.Link /> Copy link</button>
            {doc.hasMermaid && <span className="chip"><I.Tree /> contains diagram</span>}
          </div>
        </div>

        <div className="tabs" role="tablist" aria-label="Document views">
          {[["document", "Document", <I.Doc />], ["diagrams", "Diagrams", <I.Branch />], ["metadata", "Metadata", <I.Tag />], ["versions", "Versions", <I.Clock />]].map(([id, label, icon]) => (
            <button key={id} role="tab" aria-selected={tab === id} onClick={() => setTab(id)}>{icon}{label}</button>
          ))}
        </div>

        {tab === "document" && (
          <article className="md" ref={bodyRef} dangerouslySetInnerHTML={{ __html: doc.bodyHtml }} />
        )}

        {tab === "diagrams" && (
          <div>
            <RelationshipGraph doc={doc} onOpenRef={onOpenDoc} />
            <StructureMap headings={doc.headings} onJump={jump} />
            {doc.hasMermaid && <p className="muted" style={{ fontSize: "var(--uk-type-body-small)" }}>This document also embeds authored diagrams — see the <button className="btn btn--ghost btn--sm" onClick={() => setTab("document")}>Document</button> tab.</p>}
          </div>
        )}

        {tab === "metadata" && <MetadataBoard doc={doc} onOpenRef={onOpenDoc} />}

        {tab === "versions" && (
          <div>
            <div className="ctxcard"><h4 className="ctxcard__label">Lifecycle stage</h4><LifecycleStepper stage={doc.statusStage} /></div>
            <div className="ctxcard"><h4 className="ctxcard__label">Version</h4>
              <div className="metaboard">
                <div className="metafield"><span className="metafield__k">Current version</span><span className="metafield__v mono">{doc.version || "—"}</span></div>
                <div className="metafield"><span className="metafield__k">Last updated</span><span className="metafield__v mono">{fmtDate(doc.lastUpdated)}</span></div>
              </div>
            </div>
            <div className="ctxcard"><h4 className="ctxcard__label">Lineage</h4><LineageTimeline doc={doc} onOpenRef={onOpenDoc} /></div>
          </div>
        )}

        <footer className="pagefoot" style={{ marginTop: "var(--uk-space-48)", paddingTop: "var(--uk-space-24)", borderTop: "1px solid var(--uk-color-border-subtle)", display: "flex", justifyContent: "space-between", gap: "var(--uk-space-12)", flexWrap: "wrap" }}>
          {prev ? <button className="btn btn--ghost btn--sm" onClick={() => onOpenDoc(prev)}><I.ArrowLeft /> {shortText(prev.title, 28)}</button> : <span />}
          {next ? <button className="btn btn--ghost btn--sm" onClick={() => onOpenDoc(next)}>{shortText(next.title, 28)} <I.ArrowRight /></button> : <span />}
        </footer>
      </main>

      <aside className="viewer__context" aria-label="Document context">
        <div className="ctxcard">
          <h4 className="ctxcard__label">At a glance</h4>
          <div style={{ display: "flex", flexWrap: "wrap", gap: "var(--uk-space-6)" }}>
            <DomainChips domains={doc.domains} max={3} />
          </div>
        </div>
        <div className="ctxcard">
          <h4 className="ctxcard__label">Relationships</h4>
          <RelMiniSummary doc={doc} onOpenDoc={onOpenDoc} onSeeAll={() => setTab("diagrams")} />
        </div>
      </aside>
    </div>
  );
}

function RelMiniSummary({ doc, onOpenDoc, onSeeAll }) {
  const rel = doc.relationships || {};
  const counts = [["Derived from", (rel.derivedFrom || []).length], ["Related", (rel.relatedSpecs || []).length], ["Referenced by", (rel.referencedBy || []).length]].filter(([, n]) => n);
  if (!counts.length) return <p className="muted" style={{ fontSize: "var(--uk-type-body-small)" }}>No linked documents.</p>;
  const top = [].concat(rel.derivedFrom || [], rel.relatedSpecs || [], rel.referencedBy || []).filter((r) => r.title || r.collection).slice(0, 5);
  return (
    <div>
      <div style={{ display: "flex", flexWrap: "wrap", gap: "var(--uk-space-6)", marginBottom: "var(--uk-space-12)" }}>
        {counts.map(([l, n]) => <span className="chip" key={l}>{l} <span className="mono">{n}</span></span>)}
      </div>
      <div className="reflist">
        {top.map((r, i) => <a key={i} href="#" onClick={(e) => { e.preventDefault(); onOpenDoc(r); }} title={r.id}>{collectionIcon(r.collection)}<span style={{ flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{r.title || r.id}</span></a>)}
      </div>
      <button className="btn btn--ghost btn--sm" style={{ marginTop: "var(--uk-space-8)" }} onClick={onSeeAll}>See relationship graph <I.ArrowRight /></button>
    </div>
  );
}

Object.assign(window, { Viewer, safeId, MetadataBoard });
