// Documentation shell — DocsPage, sidebar, TOC.
//
// Content is ALWAYS pulled from the database via /docs/api.php — the same
// endpoint (and help-* tables) that power the public /docs/ site. There is no
// static fallback: if the API is unreachable the page shows an error state.
const { useState: useStateD, useEffect: useEffectD, useRef: useRefD } = React;

// ---------------------------------------------------------------------------
// Data source
//   ?action=tree                 → whole sidebar (categories → subcategories)
//   ?action=page&cat=&sub=       → one subcategory's articles (HTMLPurifier-sanitised)
// ---------------------------------------------------------------------------
const DOCS_API = (typeof window !== "undefined" && window.DOCS_API_BASE) || "docs/api.php";

function useDocsTree() {
  const [state, setState] = useStateD({ status: "loading", tree: null });
  useEffectD(() => {
    let alive = true;
    fetch(DOCS_API + "?action=tree", { headers: { Accept: "application/json" } })
      .then(r => (r.ok ? r.json() : Promise.reject(r.status)))
      .then(j => {
        if (!alive) return;
        if (j && j.ok && j.data && Array.isArray(j.data.categories) && j.data.categories.length) {
          setState({ status: "ready", tree: j.data.categories });
        } else {
          setState({ status: "error", tree: null });
        }
      })
      .catch(() => { if (alive) setState({ status: "error", tree: null }); });
    return () => { alive = false; };
  }, []);
  return state;
}

// Build the sidebar nav shape ({section, items:[{id,label}]}) from the DB tree.
// item id is "<categoryUrlpath>/<subcategoryUrlpath>".
function buildNavFromTree(tree) {
  return tree.map(c => ({
    section: c.name,
    items: c.subcategories.map(s => ({ id: c.urlpath + "/" + s.urlpath, label: s.name })),
  }));
}

// Fetches + renders one subcategory "page" (its articles) from the API.
function DbArticle({ slug, onReady }) {
  const [state, setState] = useStateD({ status: "loading", data: null });
  const ref = useRefD(null);

  useEffectD(() => {
    let alive = true;
    setState({ status: "loading", data: null });
    const [cat, sub] = slug.split("/");
    fetch(DOCS_API + "?action=page&cat=" + encodeURIComponent(cat) + "&sub=" + encodeURIComponent(sub))
      .then(r => (r.ok ? r.json() : Promise.reject(r.status)))
      .then(j => {
        if (!alive) return;
        if (j && j.ok) setState({ status: "ok", data: j.data });
        else setState({ status: "error", data: null });
      })
      .catch(() => { if (alive) setState({ status: "error", data: null }); });
    return () => { alive = false; };
  }, [slug]);

  // After content renders, give every heading a stable id so the TOC + deep
  // links work regardless of whether the stored HTML included ids.
  useEffectD(() => {
    if (state.status !== "ok" || !ref.current) return;
    const used = {};
    ref.current.querySelectorAll("h2, h3").forEach(h => {
      let base = (h.textContent || "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "section";
      let id = base; let n = 2;
      while (used[id]) { id = base + "-" + n++; }
      used[id] = true;
      h.id = id;
    });
    onReady && onReady();
  }, [state.status, slug]);

  if (state.status === "loading") {
    return (
      <div className="docs-skeleton">
        <div className="sk sk-h1"></div>
        <div className="sk sk-lead"></div>
        <div className="sk sk-p"></div><div className="sk sk-p"></div><div className="sk sk-p short"></div>
        <div className="sk sk-h2"></div>
        <div className="sk sk-p"></div><div className="sk sk-p short"></div>
      </div>
    );
  }
  if (state.status === "error") {
    return (
      <>
        <h1>Article unavailable</h1>
        <p className="lead">We couldn't load this article from the documentation database. Please try again shortly.</p>
      </>
    );
  }

  const d = state.data;
  const arts = d.articles || [];
  return (
    <div ref={ref}>
      <h1>{d.subcategory.name}</h1>
      {d.subcategory.description ? <p className="lead">{d.subcategory.description}</p> : null}
      {arts.length === 0 && <p>This section is being written — check back soon.</p>}
      {arts.map((a, i) => (
        <section key={a.id} className="db-article">
          {arts.length > 1 && <h2>{a.name}</h2>}
          <div dangerouslySetInnerHTML={{ __html: a.content_html }} />
        </section>
      ))}
    </div>
  );
}

// Right-side TOC — extracts h2/h3 headings from current content
function DocsTOC({ pageId, signal }) {
  const [active, setActive] = useStateD("");
  const [items, setItems] = useStateD([]);

  useEffectD(() => {
    const grab = () => {
      const root = document.querySelector(".docs-content");
      if (!root) return;
      const hs = [...root.querySelectorAll("h2[id], h3[id]")];
      setItems(hs.map(h => ({ id: h.id, label: h.textContent, level: h.tagName === "H2" ? 2 : 3 })));
    };
    const t = setTimeout(grab, 0);
    return () => clearTimeout(t);
  }, [pageId, signal]);

  useEffectD(() => {
    const onScroll = () => {
      const root = document.querySelector(".docs-content");
      if (!root) return;
      const hs = [...root.querySelectorAll("h2[id], h3[id]")];
      let current = "";
      for (const h of hs) {
        if (h.getBoundingClientRect().top < 120) current = h.id;
        else break;
      }
      setActive(current);
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, [pageId, items, signal]);

  if (!items.length) return <div className="docs-toc" />;

  return (
    <div className="docs-toc">
      <h6>On this page</h6>
      {items.map(it => (
        <a
          key={it.id}
          href={`#${it.id}`}
          className={active === it.id ? "is-active" : ""}
          style={{ paddingLeft: it.level === 3 ? 24 : 12 }}
          onClick={(e) => {
            e.preventDefault();
            const target = document.getElementById(it.id);
            if (target) {
              const top = target.getBoundingClientRect().top + window.scrollY - 80;
              window.scrollTo({ top, behavior: "smooth" });
            }
          }}
        >{it.label}</a>
      ))}
    </div>
  );
}

function DocsSidebar({ nav, active, onPick, query, onQuery }) {
  // Filter sections by query
  const q = (query || "").toLowerCase().trim();
  const filtered = !q ? nav : nav
    .map(sec => ({ ...sec, items: sec.items.filter(it => it.label.toLowerCase().includes(q) || sec.section.toLowerCase().includes(q)) }))
    .filter(sec => sec.items.length);

  return (
    <aside className="docs-sidebar">
      <div className="docs-search">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.35-4.35"/></svg>
        <input placeholder="Search docs…" value={query} onChange={(e) => onQuery(e.target.value)} />
        <kbd>⌘K</kbd>
      </div>
      {filtered.map(sec => (
        <React.Fragment key={sec.section}>
          <h5 dangerouslySetInnerHTML={{ __html: sec.section }} />
          {sec.items.map(it => (
            <a
              key={it.id}
              href={`#/docs/${it.id}`}
              className={active === it.id ? "is-active" : ""}
              onClick={(e) => { e.preventDefault(); onPick(it.id); }}
              dangerouslySetInnerHTML={{ __html: it.label }}
            />
          ))}
        </React.Fragment>
      ))}
      {!filtered.length && (
        <div style={{ padding: 16, fontSize: 13, color: "var(--fg-muted)" }}>No matches.</div>
      )}
    </aside>
  );
}

// Full-page shell used for the loading + error states (keeps layout stable).
function DocsStateShell({ children }) {
  return (
    <div className="page">
      <div className="container">
        <div className="docs-shell">
          <aside className="docs-sidebar">
            {[...Array(6)].map((_, i) => <div key={i} className="sk sk-nav" style={{ animationDelay: (i * 60) + "ms" }}></div>)}
          </aside>
          <article className="docs-content">{children}</article>
          <div className="docs-toc" />
        </div>
      </div>
    </div>
  );
}

function DocsPage({ pageId, onNavigate }) {
  const [query, setQuery] = useStateD("");
  const [tocSignal, setTocSignal] = useStateD(0);
  const { status, tree } = useDocsTree();

  // While the tree request is in flight, show a light shell (no content flash).
  if (status === "loading") {
    return (
      <DocsStateShell>
        <div className="docs-skeleton">
          <div className="sk sk-h1"></div>
          <div className="sk sk-lead"></div>
          <div className="sk sk-p"></div><div className="sk sk-p"></div><div className="sk sk-p short"></div>
        </div>
      </DocsStateShell>
    );
  }

  // No static fallback — if the DB/API can't be reached, say so.
  if (status === "error") {
    return (
      <DocsStateShell>
        <h1>Documentation unavailable</h1>
        <p className="lead">We couldn't reach the documentation service right now. Please try again shortly.</p>
      </DocsStateShell>
    );
  }

  const nav = buildNavFromTree(tree);
  const order = nav.flatMap(s => s.items.map(i => i.id));
  const first = order[0];
  const valid = order.includes(pageId) ? pageId : first;

  const idx = order.indexOf(valid);
  const prev = idx > 0 ? order[idx - 1] : null;
  const next = idx >= 0 && idx < order.length - 1 ? order[idx + 1] : null;
  const labelOf = (id) => nav.flatMap(s => s.items).find(i => i.id === id)?.label || id;
  const sectionOf = (id) => nav.find(s => s.items.some(i => i.id === id))?.section || "";

  const go = (id) => {
    window.location.hash = "/docs/" + id;
    window.scrollTo({ top: 0, behavior: "instant" });
  };

  return (
    <div className="page">
      <div className="container">
        <div className="docs-shell">
          <DocsSidebar nav={nav} active={valid} onPick={go} query={query} onQuery={setQuery} />
          <article className="docs-content">
            <div className="mono" style={{ color: "var(--fg-faint)", marginBottom: 16, fontSize: 11, letterSpacing: "0.06em", textTransform: "uppercase" }}>
              Docs / <span dangerouslySetInnerHTML={{ __html: sectionOf(valid) }} /> / <span style={{ color: "var(--fg-muted)" }} dangerouslySetInnerHTML={{ __html: labelOf(valid) }} />
            </div>
            <DbArticle key={valid} slug={valid} onReady={() => setTocSignal(s => s + 1)} />
            <div className="docs-page-foot">
              {prev ? (
                <a href={`#/docs/${prev}`} onClick={(e) => { e.preventDefault(); go(prev); }}>
                  <small>← Previous</small>
                  <span dangerouslySetInnerHTML={{ __html: labelOf(prev) }} />
                </a>
              ) : <span style={{ flex: 1 }}></span>}
              {next ? (
                <a className="next" href={`#/docs/${next}`} onClick={(e) => { e.preventDefault(); go(next); }}>
                  <small>Next →</small>
                  <span dangerouslySetInnerHTML={{ __html: labelOf(next) }} />
                </a>
              ) : <span style={{ flex: 1 }}></span>}
            </div>
          </article>
          <DocsTOC pageId={valid} signal={tocSignal} />
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { DocsPage });
