/* ===========================================================
   Reiktable — Rulebooks: client-side PDF upload, index & search
   PDFs never leave the browser (stored in IndexedDB).
   Exposes (window): RulesView
   =========================================================== */
(function () {
  const { useState, useEffect, useRef } = React;

  function escapeRe(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }

  // build a highlighted snippet around the first matched term
  function snippet(text, terms) {
    const lower = text.toLowerCase();
    let idx = -1, hit = "";
    for (const t of terms) { const i = lower.indexOf(t); if (i >= 0 && (idx < 0 || i < idx)) { idx = i; hit = t; } }
    if (idx < 0) idx = 0;
    const start = Math.max(0, idx - 90), end = Math.min(text.length, idx + 220);
    let frag = (start > 0 ? "… " : "") + text.slice(start, end).replace(/\s+/g, " ").trim() + (end < text.length ? " …" : "");
    const re = new RegExp("(" + terms.map(escapeRe).join("|") + ")", "gi");
    const parts = frag.split(re);
    return parts.map((p, i) => terms.some(t => p.toLowerCase() === t) ? <mark key={i}>{p}</mark> : <span key={i}>{p}</span>);
  }

  window.RulesView = function () {
    const [s, update] = window.useStore();
    const [books, setBooks] = useState([]);     // {id,name,pages:[{n,text}],blobUrl,size}
    const [loading, setLoading] = useState(true);
    const [q, setQ] = useState("");
    const [scope, setScope] = useState("all");  // book id or 'all'
    const [progress, setProgress] = useState(null);
    const fileRef = useRef();

    // load indexed books from IndexedDB on mount
    useEffect(() => {
      (async () => {
        try {
          const recs = await window.PDFDB.all();
          setBooks(recs.map(r => ({ ...r, blobUrl: URL.createObjectURL(r.blob) })));
        } catch (e) {}
        setLoading(false);
      })();
    }, []);

    async function handleFiles(files) {
      const lib = window.pdfjsLib;
      if (!lib) { window.toast("PDF engine not ready, try again.", "red"); return; }
      for (const file of files) {
        if (!/\.pdf$/i.test(file.name)) { window.toast(`${file.name} is not a PDF`, "amb"); continue; }
        try {
          setProgress({ name: file.name, page: 0, total: 0 });
          const buf = await file.arrayBuffer();
          const pdf = await lib.getDocument({ data: buf.slice(0) }).promise;
          const total = pdf.numPages, pages = [];
          for (let n = 1; n <= total; n++) {
            const page = await pdf.getPage(n);
            const tc = await page.getTextContent();
            const text = tc.items.map(it => it.str).join(" ");
            pages.push({ n, text });
            if (n % 3 === 0 || n === total) setProgress({ name: file.name, page: n, total });
          }
          const rec = { id: window.WFRP.ID(), name: file.name.replace(/\.pdf$/i, ""), pages, blob: file, size: file.size, addedAt: Date.now() };
          await window.PDFDB.put(rec);
          setBooks(b => [...b, { ...rec, blobUrl: URL.createObjectURL(file) }]);
          update(st => { st.library = [...(st.library || []), { id: rec.id, name: rec.name, pages: total, size: file.size }]; });
          window.toast(`Indexed “${rec.name}” — ${total} pages`, "grn");
        } catch (e) {
          window.toast(`Failed to index ${file.name}`, "red");
        }
      }
      setProgress(null);
    }

    async function removeBook(id) {
      await window.PDFDB.del(id);
      setBooks(b => b.filter(x => x.id !== id));
      update(st => { st.library = (st.library || []).filter(x => x.id !== id); });
    }

    function openPage(book, n) {
      window.open(`${book.blobUrl}#page=${n}`, "_blank");
    }

    // search
    const terms = q.toLowerCase().split(/\s+/).filter(t => t.length > 1);
    const results = [];
    if (terms.length) {
      const pool = scope === "all" ? books : books.filter(b => b.id === scope);
      for (const b of pool) {
        for (const pg of b.pages) {
          const low = pg.text.toLowerCase();
          if (terms.every(t => low.includes(t))) {
            let score = 0; for (const t of terms) score += (low.split(t).length - 1);
            results.push({ book: b, page: pg, score });
          }
        }
      }
      results.sort((a, b) => b.score - a.score);
    }
    const shown = results.slice(0, 80);

    return (
      <div className="content fade-in">
        <div className="page-h">
          <div><h1>Rulebooks</h1><p>Full-text search across your own PDFs. Everything is indexed locally — files never leave this browser.</p></div>
          <div className="sp"></div>
          <Btn className="pri" icon="upload" onClick={() => fileRef.current.click()}>Add PDF</Btn>
          <input ref={fileRef} type="file" accept="application/pdf" multiple style={{ display: "none" }}
            onChange={e => { handleFiles([...e.target.files]); e.target.value = ""; }} />
        </div>

        {/* search bar */}
        <div className="searchbar" style={{ marginBottom: 14 }}>
          <Icon name="search" />
          <input placeholder={books.length ? "Search rules… e.g. “opposed test”, “critical wound”, “channelling”" : "Add a rulebook PDF first to search it"}
            value={q} onChange={e => setQ(e.target.value)} disabled={!books.length} />
          {books.length > 0 && (
            <select className="input sm" style={{ width: 180 }} value={scope} onChange={e => setScope(e.target.value)}>
              <option value="all">All books ({books.length})</option>
              {books.map(b => <option key={b.id} value={b.id}>{b.name}</option>)}
            </select>
          )}
        </div>

        {progress && (
          <div className="card" style={{ marginBottom: 14 }}><div className="card-b center g12">
            <Spinner size={18} />
            <div className="grow"><b>Indexing {progress.name}</b>
              <div className="bar" style={{ marginTop: 6 }}><i style={{ width: progress.total ? (progress.page / progress.total * 100) + "%" : "5%" }}></i></div>
            </div>
            <span className="mono tx3">{progress.page}/{progress.total || "…"}</span>
          </div></div>
        )}

        <div className="cols s">
          <div>
            {terms.length > 0 ? (
              <div className="col g10">
                <div className="eyebrow" style={{ marginBottom: 4 }}>{results.length} match{results.length === 1 ? "" : "es"}{results.length > 80 ? " · showing 80" : ""}</div>
                {shown.length === 0 && <Empty icon="search" title="No matches">Try fewer or different words.</Empty>}
                {shown.map((r, i) => (
                  <div key={i} className="result" onClick={() => openPage(r.book, r.page.n)}>
                    <div className="meta"><span className="hl">{r.book.name}</span><span>Page {r.page.n}</span><span>· {r.score} hit{r.score === 1 ? "" : "s"}</span><span style={{ marginLeft: "auto" }}><Icon name="eye" size={13} /></span></div>
                    <div className="snip">{snippet(r.page.text, terms)}</div>
                  </div>
                ))}
              </div>
            ) : (
              <div className="card"><div className="card-b">
                {loading ? <div className="center g10 tx3"><Spinner /> Loading library…</div> :
                  books.length === 0 ? (
                    <Empty icon="book" title="No rulebooks yet">
                      <div style={{ maxWidth: 420, margin: "0 auto" }}>Upload your own WFRP PDFs (Core Rulebook, Up in Arms, Archives, your homebrew). They’re parsed page-by-page and indexed for instant search. Nothing is uploaded to a server.</div>
                      <Btn className="pri mt14" icon="upload" onClick={() => fileRef.current.click()}>Add your first PDF</Btn>
                    </Empty>
                  ) : (
                    <div className="tx3" style={{ textAlign: "center", padding: 20 }}>Type above to search {books.reduce((a, b) => a + b.pages.length, 0)} pages across {books.length} book{books.length === 1 ? "" : "s"}.</div>
                  )}
              </div></div>
            )}
          </div>

          <div className="card" style={{ alignSelf: "start" }}>
            <div className="card-h"><Icon name="book" /><h3>Library</h3><span className="x">{books.length}</span></div>
            <div className="card-b tight">
              {books.length === 0 && <div className="tx3" style={{ padding: 12, fontSize: 13 }}>Empty. Add PDFs to build your searchable library.</div>}
              {books.map(b => (
                <div key={b.id} className="book-pill" style={{ marginBottom: 8 }}>
                  <Icon name="file" size={16} style={{ color: "var(--acc-2)" }} />
                  <div className="grow" style={{ minWidth: 0 }}>
                    <div style={{ fontSize: 13.5, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{b.name}</div>
                    <div className="mono tx3" style={{ fontSize: 11 }}>{b.pages.length} pages · {(b.size / 1048576).toFixed(1)} MB</div>
                  </div>
                  <button className="btn icon sm ghost" title="Open" onClick={() => window.open(b.blobUrl, "_blank")}><Icon name="eye" size={14} /></button>
                  <button className="btn icon sm ghost" title="Remove" onClick={() => removeBook(b.id)}><Icon name="trash" size={14} /></button>
                </div>
              ))}
              <div className="divider"></div>
              <div className="tx3" style={{ fontSize: 12, padding: "0 6px 6px", lineHeight: 1.5 }}>
                <b className="tx2">Tip:</b> search is AND-based — every word must appear on the page. Click a result to open the PDF at that page.
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  };
})();
