import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { marked } from 'marked';
import ConsoleShell from './ConsoleShell.jsx';
import { api } from './api.js';
import { copyToast } from './toast.js';

marked.setOptions({ breaks: true });

// Minimal, safe syntax highlighter: strings, numbers, keywords only.
// No comment rule on purpose — '//' inside URLs (https://) must never be eaten.
const esc = (s) => s.replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));
const HL_RE = /("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)|(\b\d+(?:\.\d+)?\b)|\b(const|let|var|function|return|while|do|done|if|then|else|fi|for|true|false|null|async|await|new|export|import|setInterval|setTimeout|fetch|sleep|curl|JSON|Math|console)\b/g;
function highlightCode(code) {
  let out = '', last = 0, m;
  HL_RE.lastIndex = 0;
  while ((m = HL_RE.exec(code))) {
    out += esc(code.slice(last, m.index));
    const cls = m[1] ? 'hl-str' : m[2] ? 'hl-num' : 'hl-kw';
    out += `<span class="${cls}">${esc(m[0])}</span>`;
    last = m.index + m[0].length;
  }
  return out + esc(code.slice(last));
}

// URL-safe slug from a doc title, for shareable #/<slug> doc links.
const slugify = (s) => String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'doc';

// A context snippet around a full-text match, markdown stripped, query <mark>ed.
function makeSnippet(body, idx, query) {
  const start = Math.max(0, idx - 48);
  const end = Math.min(body.length, idx + query.length + 64);
  const raw = body.slice(start, end).replace(/[#>*`~_[\]]/g, '').replace(/\s+/g, ' ').trim();
  const esc = (s) => s.replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));
  const re = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'ig');
  return (start > 0 ? '…' : '') + esc(raw).replace(re, '<mark>$1</mark>') + (end < body.length ? '…' : '');
}

function DocsWorkspace({ canEdit }) {
  const [docs, setDocs] = useState([]);
  const [sel, setSel] = useState(null);
  const [doc, setDoc] = useState(null);
  const [editing, setEditing] = useState(false);
  const [draft, setDraft] = useState({ title: '', folder: '', body: '' });
  const [err, setErr] = useState('');
  const [q, setQ] = useState('');
  const [navOpen, setNavOpen] = useState(false);
  const [results, setResults] = useState(null);
  const renderRef = useRef(null);
  const pendingSection = useRef(null);
  const bodiesRef = useRef(null);

  const loadList = useCallback(async () => {
    try {
      const d = await api('/docs'); const list = d.docs || []; setDocs(list);
      // hash is #/<doc-slug> or #/<doc-slug>/<section-slug>; land on the named doc
      const raw = window.location.hash.replace(/^#\/?/, '');
      const docSlug = raw.split('/')[0];
      pendingSection.current = raw.split('/').slice(1).join('/') || null;
      const fromHash = docSlug && list.find((x) => slugify(x.title) === docSlug);
      const def = fromHash || list.find((x) => /quickstart/i.test(x.title)) || list.find((x) => x.folder === 'start here') || list[0];
      if (def) setSel((cur) => cur ?? def.id);
    } catch (e) { setErr(e.message); }
  }, []);
  useEffect(() => { loadList(); }, [loadList]);
  // shareable doc + section URLs: #/<doc-slug> or #/<doc-slug>/<section-slug>
  const openDoc = useCallback((d) => {
    setSel(d.id);
    setNavOpen(false);
    pendingSection.current = null;
    const slug = slugify(d.title);
    if (window.location.hash !== `#/${slug}`) window.history.pushState(null, '', `${window.location.pathname}${window.location.search}#/${slug}`);
  }, []);
  const goSection = useCallback((id) => {
    const el = renderRef.current?.querySelector(`#${CSS.escape(id)}`);
    if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
    const docSlug = doc ? slugify(doc.title) : '';
    if (docSlug) window.history.pushState(null, '', `${window.location.pathname}${window.location.search}#/${docSlug}/${id}`);
  }, [doc]);
  useEffect(() => {
    const onHash = () => {
      const raw = window.location.hash.replace(/^#\/?/, '');
      const docSlug = raw.split('/')[0];
      const section = raw.split('/').slice(1).join('/') || null;
      const d = docs.find((x) => slugify(x.title) === docSlug);
      if (!d) return;
      pendingSection.current = section;
      setSel(d.id);
      if (section) { const el = renderRef.current?.querySelector(`#${CSS.escape(section)}`); if (el) el.scrollIntoView({ block: 'start' }); }
    };
    window.addEventListener('hashchange', onHash);
    window.addEventListener('popstate', onHash);
    return () => { window.removeEventListener('hashchange', onHash); window.removeEventListener('popstate', onHash); };
  }, [docs]);
  useEffect(() => {
    if (sel == null) { setDoc(null); return; }
    api(`/docs/${sel}`).then((d) => { setDoc(d.doc); setDraft({ title: d.doc.title, folder: d.doc.folder, body: d.doc.body }); setEditing(false); document.title = `${d.doc.title} · Docs · amerc`; }).catch((e) => setErr(e.message));
  }, [sel]);
  // Build the rendered HTML once per doc: parse markdown, then bake heading ids +
  // shareable anchor links and code-block copy buttons directly into the string.
  // Doing it at the string level (not post-render DOM mutation) means React can
  // never wipe the decorations on a re-render. Also yields the on-this-page TOC.
  const { docHtml, toc } = useMemo(() => {
    if (!doc) return { docHtml: '', toc: [] };
    let h = marked.parse(doc.body || '');
    const entries = []; const used = {};
    const dslug = slugify(doc.title);
    h = h.replace(/<(h[23])>([\s\S]*?)<\/\1>/g, (m, tag, inner) => {
      const text = inner.replace(/<[^>]+>/g, '').trim();
      let sid = slugify(text);
      if (used[sid]) { used[sid] += 1; sid = `${sid}-${used[sid]}`; } else used[sid] = 1;
      entries.push({ id: sid, text, level: tag === 'h3' ? 3 : 2 });
      return `<${tag} id="${sid}">${inner}<a class="docs-anchor" href="#/${dslug}/${sid}" title="Link to this section" aria-label="Link to section ${text.replace(/"/g, '')}">#</a></${tag}>`;
    });
    h = h.replace(/<pre><code(?:\s[^>]*)?>([\s\S]*?)<\/code><\/pre>/g, (m, code) => {
      const rawCode = code.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, '&');
      return `<div class="docs-code"><button type="button" class="docs-copy" data-copy>copy</button><pre><code>${highlightCode(rawCode)}</code></pre></div>`;
    });
    return { docHtml: h, toc: entries };
  }, [doc]);
  // copy buttons + anchor links inside the rendered HTML, via event delegation
  const onDocClick = useCallback((e) => {
    const copy = e.target.closest && e.target.closest('[data-copy]');
    if (copy) { const pre = copy.parentElement.querySelector('pre'); if (pre) copyToast(pre.innerText, 'Copied'); return; }
    const anc = e.target.closest && e.target.closest('.docs-anchor');
    if (anc) { e.preventDefault(); goSection(anc.getAttribute('href').split('/').pop()); }
  }, [goSection]);
  // after a doc renders, honor a pending #/<doc>/<section> deep-link scroll
  useEffect(() => {
    if (!docHtml || !pendingSection.current) return;
    const s = pendingSection.current; pendingSection.current = null;
    // two frames so code highlighting / reflow settles before we measure the target
    requestAnimationFrame(() => requestAnimationFrame(() => {
      const el = renderRef.current?.querySelector(`#${CSS.escape(s)}`); if (el) el.scrollIntoView({ block: 'start' });
    }));
  }, [docHtml]);
  // full-text search: fetch all doc bodies once, then match title + body, with snippets
  useEffect(() => { bodiesRef.current = null; }, [docs]);
  useEffect(() => {
    const query = q.trim();
    if (!query) { setResults(null); return; }
    let live = true;
    (async () => {
      if (!bodiesRef.current) {
        const all = await Promise.all(docs.map((d) => api(`/docs/${d.id}`).then((r) => [d.id, r.doc.body || '']).catch(() => [d.id, ''])));
        if (!live) return;
        bodiesRef.current = Object.fromEntries(all);
      }
      const ql = query.toLowerCase();
      const res = [];
      for (const d of docs) {
        const body = bodiesRef.current[d.id] || '';
        const inMeta = (d.title + ' ' + (d.folder || '')).toLowerCase().includes(ql);
        const idx = body.toLowerCase().indexOf(ql);
        if (inMeta || idx >= 0) res.push({ ...d, snippet: idx >= 0 ? makeSnippet(body, idx, query) : '' });
      }
      if (live) setResults(res);
    })();
    return () => { live = false; };
  }, [q, docs]);

  const create = async () => {
    const title = window.prompt('Document title', 'New document'); if (!title) return;
    try { const r = await api('/docs', { method: 'POST', body: { title, folder: '', body: `# ${title}\n\n` } }); await loadList(); setSel(r.id); setEditing(true); } catch (e) { setErr(e.message); }
  };
  const save = async () => { try { await api(`/docs/${sel}`, { method: 'PATCH', body: draft }); await loadList(); setDoc({ ...doc, ...draft }); setEditing(false); } catch (e) { setErr(e.message); } };
  const del = async () => { if (!window.confirm('Delete document?')) return; try { await api(`/docs/${sel}`, { method: 'DELETE' }); setSel(null); loadList(); } catch (e) { setErr(e.message); } };

  const folders = {};
  docs.forEach((d) => { (folders[d.folder || ''] = folders[d.folder || ''] || []).push(d); });

  return (
    <div className="docs">
      <aside className="docs-side">
        <button className="docs-nav-toggle" type="button" onClick={() => setNavOpen((v) => !v)} aria-expanded={navOpen}>
          <span className="docs-nav-cur">{doc ? doc.title : 'Documents'}</span>
          <span className="docs-nav-chev" aria-hidden="true">▾</span>
        </button>
        <div className={`docs-side-body${navOpen ? ' open' : ''}`}>
          <div className="docs-side-top">
            <input placeholder="search docs…" value={q} onChange={(e) => setQ(e.target.value)} />
            {canEdit && <button className="cs-btn cs-primary" onClick={create}>+ New</button>}
          </div>
          {q.trim() ? (
            <div className="docs-results">
              {results === null && <p className="cs-hint">Searching…</p>}
              {results !== null && !results.length && <p className="cs-hint">No matches for “{q.trim()}”.</p>}
              {results !== null && results.map((d) => (
                <button key={d.id} className={`docs-result${sel === d.id ? ' on' : ''}`} onClick={() => openDoc(d)}>
                  <span className="docs-result-title">{d.title}</span>
                  {d.snippet && <span className="docs-result-snip" dangerouslySetInnerHTML={{ __html: d.snippet }} />}
                </button>
              ))}
            </div>
          ) : (
            <div className="docs-tree">
              {Object.keys(folders).sort().map((f) => (
                <div key={f} className="docs-folder">
                  <div className="docs-folder-name">{f || '📁 root'}</div>
                  {folders[f].map((d) => (
                    <button key={d.id} className={`docs-item${sel === d.id ? ' on' : ''}`} onClick={() => openDoc(d)}>{d.title}</button>
                  ))}
                </div>
              ))}
              {!docs.length && <p className="cs-hint">No documents yet. Create one — humans and agents (via API key) can both read & write here.</p>}
            </div>
          )}
        </div>
      </aside>
      <section className="docs-main">
        {err && <p className="cs-err">{err}</p>}
        {!doc && <div className="docs-empty">Select or create a document.</div>}
        {doc && (
          <>
            <div className="docs-head">
              {editing
                ? <input className="docs-title-input" value={draft.title} onChange={(e) => setDraft({ ...draft, title: e.target.value })} />
                : <h2>{doc.title}</h2>}
              <div className="docs-actions">
                {editing && <input className="docs-folder-input" placeholder="folder" value={draft.folder} onChange={(e) => setDraft({ ...draft, folder: e.target.value })} />}
                {canEdit && !editing && <button className="cs-btn" onClick={() => setEditing(true)}>Edit</button>}
                {editing && <button className="cs-btn cs-primary" onClick={save}>Save</button>}
                {editing && <button className="cs-btn" onClick={() => { setEditing(false); setDraft({ title: doc.title, folder: doc.folder, body: doc.body }); }}>Cancel</button>}
                {canEdit && <button className="cs-btn" onClick={del}>Delete</button>}
              </div>
            </div>
            <p className="docs-byline">updated by {doc.updated_by || '—'}</p>
            {!editing && (
              // always rendered in reading mode (hidden when too few sections) so it
              // keeps a stable child slot — otherwise inserting it would shift the
              // <article> index and remount it, wiping the heading/code decorations.
              <nav className="docs-toc" aria-label="On this page" hidden={toc.length < 3}>
                <span className="docs-toc-h">On this page</span>
                {toc.map((t) => (
                  <button key={t.id} type="button" className={`docs-toc-link lvl${t.level}`} onClick={() => goSection(t.id)}>{t.text}</button>
                ))}
              </nav>
            )}
            {editing
              ? <textarea className="docs-editor" value={draft.body} onChange={(e) => setDraft({ ...draft, body: e.target.value })} />
              : <article ref={renderRef} className="docs-render" onClick={onDocClick} dangerouslySetInnerHTML={{ __html: docHtml }} />}
          </>
        )}
      </section>
    </div>
  );
}

export default function DocsApp() {
  return <ConsoleShell title="docs" accent="#27d8ff" current="docs" publicView>{(auth) => <DocsWorkspace canEdit={!!auth.user} />}</ConsoleShell>;
}
