/* global React, ReactDOM, Kw, parseText, Parsed, CHAPTERS,
   TweaksPanel, useTweaks, TweakSection, TweakRadio, TweakToggle, TweakSelect */

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

const API_BASE = window.API_BASE !== undefined ? window.API_BASE : "http://localhost:8000";

function lsGet(key)      { try { return JSON.parse(localStorage.getItem(key)); } catch { return null; } }
function lsSet(key, val) { try { localStorage.setItem(key, JSON.stringify(val)); } catch {} }

/* ============================================================
   PAGE RENDERERS — one function per content type.
   Adding a new tab = write a renderer here + register in RENDERERS.
   All field names match the real FastAPI schema.
   ============================================================ */

function SpellPage({ item, folio, eyebrow }) {
  const paras = (item.description || "").split("\n\n").filter(Boolean);
  return (
    <div className="page-content">
      <header className="page-header">
        <span className="page-eyebrow">{eyebrow}</span>
        <span className="page-folio">{folio}</span>
      </header>
      <h1 className="page-title">{item.name}</h1>
      <p className="page-subtitle">
        <Kw kind="school">{item.school}</Kw>
        {" · "}
        <Kw kind="circle">{item.circle + "º círculo"}</Kw>
        {" · "}
        {item.type}
      </p>
      <dl className="statblock">
        {item.casting_time && <div><dt>Execução</dt><dd><Parsed>{item.casting_time}</Parsed></dd></div>}
        {item.range       && <div><dt>Alcance</dt><dd>{item.range}</dd></div>}
        {item.target      && <div><dt>Alvo / Área</dt><dd>{item.target}</dd></div>}
        {item.duration    && <div><dt>Duração</dt><dd>{item.duration}</dd></div>}
        {item.saving_throw && <div><dt>Resistência</dt><dd><Parsed>{item.saving_throw}</Parsed></dd></div>}
      </dl>
      <div className="divider">❦</div>
      <div className="page-body">
        {paras.map((para, i) => (
          <p key={i}><Parsed>{para}</Parsed></p>
        ))}
      </div>
    </div>
  );
}

function PowerPage({ item, folio, eyebrow }) {
  const paras = (item.description || "").split("\n\n").filter(Boolean);
  return (
    <div className="page-content">
      <header className="page-header">
        <span className="page-eyebrow">{eyebrow}</span>
        <span className="page-folio">{folio}</span>
      </header>
      <h1 className="page-title">{item.name}</h1>
      <p className="page-subtitle">
        <Kw kind="power-type">{item.power_type}</Kw>
        {" · "}
        {item.power_category}
      </p>
      {item.prerequisites && (
        <dl className="statblock" style={{ gridTemplateColumns: "1fr" }}>
          <div><dt>Pré-requisito</dt><dd><Parsed>{item.prerequisites}</Parsed></dd></div>
        </dl>
      )}
      <div className="divider">❦</div>
      <div className="page-body">
        {paras.map((para, i) => (
          <p key={i}><Parsed>{para}</Parsed></p>
        ))}
      </div>
    </div>
  );
}

function EmptyPage({ folio, eyebrow }) {
  return (
    <div className="page-content">
      <header className="page-header">
        <span className="page-eyebrow">{eyebrow}</span>
        <span className="page-folio">{folio}</span>
      </header>
      <div className="empty-state">
        <div className="ornament">✦ ✦ ✦</div>
        <div>Fim do capítulo.</div>
      </div>
    </div>
  );
}

function CoverPage({ chapter }) {
  return (
    <div className="page-content">
      <div className="empty-state">
        <div className="ornament" style={{ fontSize: 60 }}>❦</div>
        <h1
          className="page-title"
          style={{ fontSize: 44, textAlign: "center", color: "var(--gilt-2)" }}
        >
          {chapter.label}
        </h1>
        <p
          className="page-subtitle"
          style={{ textAlign: "center", fontSize: 16, color: "var(--ink-2)", marginTop: 24 }}
        >
          {chapter.subtitle === "Em breve"
            ? "Este capítulo ainda está sendo escrito."
            : "Folheie pelas próximas páginas, ou use o sumário à esquerda."}
        </p>
        <div className="ornament" style={{ fontSize: 30, marginTop: 30 }}>· · ·</div>
      </div>
    </div>
  );
}

function Corners() {
  return (
    <>
      <span className="frame-corner tl" />
      <span className="frame-corner tr" />
      <span className="frame-corner bl" />
      <span className="frame-corner br" />
    </>
  );
}

const RENDERERS = {
  magias: SpellPage,
  poderes: PowerPage,
};

/* ============================================================
   FILTERING + SEARCH (client-side, on loaded data)
   ============================================================ */

function applyFilters(items, filters, query) {
  let out = items;
  for (const [groupId, values] of Object.entries(filters)) {
    if (groupId === "_defs" || !Array.isArray(values) || values.length === 0) continue;
    const def = filters._defs && filters._defs[groupId];
    if (def) out = out.filter((it) => values.some((v) => def.match(it, v)));
  }
  if (query && query.trim()) {
    const q = query.toLowerCase().trim();
    out = out.filter((it) =>
      [it.name, it.description, it.school, it.power_type, it.prerequisites]
        .filter(Boolean)
        .some((f) => f.toLowerCase().includes(q))
    );
  }
  return out;
}

async function unifiedSearch(query, limit = 12) {
  if (!query || !query.trim()) return [];
  try {
    const res = await fetch(
      `${API_BASE}/search?query=${encodeURIComponent(query)}&limit=${limit}`
    );
    const results = await res.json();
    const CHAPTER_MAP = { spell: "magias", power: "poderes" };
    return results.map((r) => ({
      item: r.data,
      chapter: (window.CHAPTERS || []).find((c) => c.id === CHAPTER_MAP[r.result_type]),
      score: r.score,
    })).filter((r) => r.chapter);
  } catch {
    return [];
  }
}

/* ============================================================
   BOOK INDEX (left margin: filters)
   ============================================================ */

function BookIndex({ chapter, filters, setFilters, counts }) {
  if (chapter.disabled) {
    return (
      <aside className="book-index">
        <div className="index-title">Sumário</div>
        <p style={{ fontStyle: "italic", color: "var(--gilt-2)", fontSize: 13 }}>
          Capítulo ainda em redação.
        </p>
      </aside>
    );
  }
  if (chapter.id === "racas") {
    const savedRace   = (() => { try { return JSON.parse(localStorage.getItem("arton_char_race"));   } catch { return null; } })();
    const savedOrigin = (() => { try { return JSON.parse(localStorage.getItem("arton_char_origin")); } catch { return null; } })();
    return (
      <aside className="book-index">
        <div className="index-title">Criação · Raças</div>
        <div className="index-group">
          <h4>Como funciona</h4>
          <p style={{ fontSize: 12.5, color: "var(--gilt-0)", margin: 0, lineHeight: 1.6 }}>
            Escolha uma <em>raça</em> da lista. Ela define bônus de atributo,
            habilidades raciais e o número de <em>slots de poder racial</em> disponíveis.
          </p>
        </div>
        {savedRace && (
          <div className="index-group">
            <h4>Raça salva</h4>
            <p style={{ fontSize: 13, color: "var(--gilt-0)", margin: 0, lineHeight: 1.5 }}>
              {savedRace.name}{savedRace.variant ? ` · ${savedRace.variant}` : ""}
              <span style={{ display: "block", fontSize: 10, letterSpacing: "0.12em", textTransform: "uppercase", color: "var(--gilt-2)", marginTop: 2 }}>{savedRace.source}</span>
            </p>
          </div>
        )}
        {savedOrigin && (
          <div className="index-group">
            <h4>Origem salva</h4>
            <p style={{ fontSize: 13, color: "var(--gilt-0)", margin: 0 }}>
              {savedOrigin.name}
            </p>
          </div>
        )}
        <div className="index-group">
          <h4>Legenda</h4>
          <p style={{ fontSize: 12, color: "var(--gilt-1)", margin: 0, lineHeight: 1.7 }}>
            <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: "var(--kw-race)", marginRight: 6 }}/>raça<br/>
            <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: "var(--kw-attribute)", marginRight: 6 }}/>atributo
          </p>
        </div>
      </aside>
    );
  }
  if (chapter.id === "origens") {
    const savedRace   = (() => { try { return JSON.parse(localStorage.getItem("arton_char_race"));   } catch { return null; } })();
    const savedOrigin = (() => { try { return JSON.parse(localStorage.getItem("arton_char_origin")); } catch { return null; } })();
    return (
      <aside className="book-index">
        <div className="index-title">Criação · Origens</div>
        <div className="index-group">
          <h4>Como funciona</h4>
          <p style={{ fontSize: 12.5, color: "var(--gilt-0)", margin: 0, lineHeight: 1.6 }}>
            A <em>origem</em> define o passado do personagem. Ela concede bônus de
            atributo, perícias treinadas e equipamentos iniciais.
          </p>
        </div>
        {savedRace && (
          <div className="index-group">
            <h4>Raça salva</h4>
            <p style={{ fontSize: 13, color: "var(--gilt-0)", margin: 0 }}>
              {savedRace.name}{savedRace.variant ? ` · ${savedRace.variant}` : ""}
            </p>
          </div>
        )}
        {savedOrigin && (
          <div className="index-group">
            <h4>Origem salva</h4>
            <p style={{ fontSize: 13, color: "var(--gilt-0)", margin: 0, lineHeight: 1.5 }}>
              {savedOrigin.name}
              <span style={{ display: "block", fontSize: 10, letterSpacing: "0.12em", textTransform: "uppercase", color: "var(--gilt-2)", marginTop: 2 }}>{savedOrigin.source}</span>
            </p>
          </div>
        )}
        <div className="index-group">
          <h4>Legenda</h4>
          <p style={{ fontSize: 12, color: "var(--gilt-1)", margin: 0, lineHeight: 1.7 }}>
            <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: "var(--kw-attribute)", marginRight: 6 }}/>atributo<br/>
            <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: "var(--kw-item)", marginRight: 6 }}/>equipamento
          </p>
        </div>
      </aside>
    );
  }
  if (chapter.id === "divindade") {
    const savedDeity = (() => { try { return JSON.parse(localStorage.getItem("arton_char_deity")); } catch { return null; } })();
    return (
      <aside className="book-index">
        <div className="index-title">Criação · Divindade</div>
        <div className="index-group">
          <h4>Como funciona</h4>
          <p style={{ fontSize: 12.5, color: "var(--gilt-0)", margin: 0, lineHeight: 1.6 }}>
            Escolha uma <em>divindade</em> patrona. Ela define os <em>poderes
            concedidos</em> disponíveis ao seu devoto e as obrigações religiosas
            do personagem.
          </p>
        </div>
        {savedDeity && (
          <div className="index-group">
            <h4>Divindade salva</h4>
            <p style={{ fontSize: 13, color: "var(--gilt-0)", margin: 0, lineHeight: 1.5 }}>
              {savedDeity.name}
              <span style={{ display: "block", fontSize: 10, letterSpacing: "0.12em", textTransform: "uppercase", color: "var(--gilt-2)", marginTop: 2 }}>{savedDeity.deity_group}</span>
              {/* suporta formato novo (chosen_powers[]) e antigo (chosen_power) */}
              {(savedDeity.chosen_powers || (savedDeity.chosen_power ? [savedDeity.chosen_power] : [])).map((p, i) => (
                <span key={i} style={{ display: "block", fontSize: 12, fontStyle: "italic", color: "var(--gilt-1)", marginTop: i === 0 ? 6 : 2 }}>
                  ✦ {p.name}
                </span>
              ))}
            </p>
          </div>
        )}
        <div className="index-group">
          <h4>Energia canalizada</h4>
          <p style={{ fontSize: 12, color: "var(--gilt-1)", margin: 0, lineHeight: 1.9 }}>
            <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: "var(--gilt-0)", marginRight: 6, verticalAlign: "middle" }}/>Positiva — cura, luz, vida<br/>
            <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: "var(--kw-school)", marginRight: 6, verticalAlign: "middle" }}/>Negativa — sombras, morte, dor
          </p>
        </div>
        <div className="index-group">
          <h4>Legenda</h4>
          <p style={{ fontSize: 12, color: "var(--gilt-1)", margin: 0, lineHeight: 1.7 }}>
            <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: "var(--kw-race)", marginRight: 6 }}/>raça<br/>
            <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: "var(--kw-class)", marginRight: 6 }}/>classe
          </p>
        </div>
      </aside>
    );
  }
  if (chapter.id === "classes") {
    return (
      <aside className="book-index">
        <div className="index-title">Criação de Personagem</div>
        <div className="index-group">
          <h4>Como funciona</h4>
          <p style={{ fontSize: 12.5, color: "var(--gilt-0)", margin: 0, lineHeight: 1.6 }}>
            Escolha uma <em>classe</em> e um <em>nível</em>. Para classes conjuradoras,
            o sistema mostra uma linha por nível — cada uma com os círculos
            de magia disponíveis <em>naquele</em> nível.
          </p>
        </div>
        <div className="index-group">
          <h4>Regra de orçamento</h4>
          <p style={{ fontSize: 12.5, color: "var(--gilt-0)", margin: 0, lineHeight: 1.6 }}>
            Picks <strong>não acumulam</strong>. Cada slot só aceita magias
            dos círculos liberados naquele nível — isto é imposto pela UI.
          </p>
        </div>
        <div className="index-group">
          <h4>Legenda</h4>
          <p style={{ fontSize: 12, color: "var(--gilt-1)", margin: 0, lineHeight: 1.7 }}>
            <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: "var(--kw-circle)", marginRight: 6 }}/>círculo<br/>
            <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: "var(--kw-school)", marginRight: 6 }}/>escola<br/>
            <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: "var(--kw-attribute)", marginRight: 6 }}/>atributo<br/>
            <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: "var(--kw-class)", marginRight: 6 }}/>classe
          </p>
        </div>
      </aside>
    );
  }
  return (
    <aside className="book-index">
      <div className="index-title">Sumário · {chapter.label}</div>
      {chapter.filterGroups.map((group) => (
        <div key={group.id} className="index-group">
          <h4>{group.label}</h4>
          <div>
            {group.values.map((v) => {
              const selected = filters[group.id] || [];
              const active = selected.includes(v);
              const c = counts[group.id] ? counts[group.id][v] : null;
              return (
                <button
                  key={v}
                  className="index-chip"
                  data-active={active}
                  onClick={() =>
                    setFilters((f) => {
                      const prev = f[group.id] || [];
                      const next = active ? prev.filter((x) => x !== v) : [...prev, v];
                      return { ...f, [group.id]: next };
                    })
                  }
                  style={group.kind ? { "--kw-color": `var(--kw-${group.kind})` } : null}
                >
                  {group.kind && (
                    <span
                      className="dot"
                      style={{ background: `var(--kw-${group.kind})` }}
                    />
                  )}
                  <span>{group.format ? group.format(v) : v}</span>
                  {c != null && <span className="index-count"> · {c}</span>}
                </button>
              );
            })}
          </div>
        </div>
      ))}
      <div className="index-group">
        <h4>Atalhos</h4>
        <p
          style={{
            fontSize: 11.5,
            fontStyle: "italic",
            color: "var(--gilt-2)",
            margin: 0,
            lineHeight: 1.7,
          }}
        >
          <kbd style={kbdStyle}>←</kbd> <kbd style={kbdStyle}>→</kbd> virar páginas
          <br />
          <kbd style={kbdStyle}>/</kbd> focar busca
          <br />
          <kbd style={kbdStyle}>Ctrl</kbd>+<kbd style={kbdStyle}>K</kbd> oráculo
          <br />
          <kbd style={kbdStyle}>Esc</kbd> sair de campos
        </p>
      </div>
    </aside>
  );
}

const kbdStyle = {
  fontFamily: "var(--font-mono)",
  fontSize: 10,
  border: "1px solid var(--gilt-2)",
  borderRadius: 3,
  padding: "0 4px",
  fontStyle: "normal",
  color: "var(--gilt-0)",
};

/* ============================================================
   TABS (right side: chapters)
   ============================================================ */

function BookTabs({ chapterId, setChapterId, chapters }) {
  return (
    <nav className="book-tabs">
      {chapters.map((ch) => (
        <button
          key={ch.id}
          className="tab"
          disabled={ch.disabled}
          data-active={ch.id === chapterId}
          style={{ "--tab-bg": ch.color, background: ch.color }}
          onClick={() => !ch.disabled && setChapterId(ch.id)}
        >
          <span>
            {ch.label}
            <small>{ch.subtitle}</small>
          </span>
        </button>
      ))}
    </nav>
  );
}

/* ============================================================
   SPREAD (the actual book pages, sliding horizontally)
   ============================================================ */

function Frame({ chapter, items, idx }) {
  const Renderer =
    RENDERERS[chapter.id] ||
    (() => <EmptyPage folio="—" eyebrow={chapter.label} />);
  const a = items[idx * 2];
  const b = items[idx * 2 + 1];
  const base = idx * 2;
  return (
    <div className="spread-frame">
      <div className="page left">
        <Corners />
        {a ? (
          <Renderer item={a} folio={`fólio ${base + 1}`} eyebrow={chapter.label} />
        ) : idx === 0 ? (
          <CoverPage chapter={chapter} />
        ) : (
          <EmptyPage folio="—" eyebrow={chapter.label} />
        )}
      </div>
      <div className="page right">
        <Corners />
        {b ? (
          <Renderer item={b} folio={`fólio ${base + 2}`} eyebrow={chapter.label} />
        ) : (
          <EmptyPage folio="—" eyebrow={chapter.label} />
        )}
      </div>
    </div>
  );
}

function Spread({ chapter, items, spreadIdx, animation }) {
  const [prev, setPrev] = useState(spreadIdx);
  const [animating, setAnimating] = useState(false);
  const [direction, setDirection] = useState(1);
  const transitionRef = useRef(null);

  useEffect(() => {
    if (spreadIdx === prev) return;
    setDirection(spreadIdx > prev ? 1 : -1);
    setAnimating(true);
    clearTimeout(transitionRef.current);
    transitionRef.current = setTimeout(() => {
      setPrev(spreadIdx);
      setAnimating(false);
    }, 560);
    return () => clearTimeout(transitionRef.current);
  }, [spreadIdx, prev]);

  if (animation === "fade") {
    return (
      <div className="pages-viewport">
        <div
          key={spreadIdx}
          className="spread-frame"
          style={{
            animation: "fadeIn 0.4s ease both",
            display: "grid",
            gridTemplateColumns: "1fr 1fr",
          }}
        >
          <Frame chapter={chapter} items={items} idx={spreadIdx} />
        </div>
      </div>
    );
  }

  // SLIDE — track holds [outgoing, incoming], both fully rendered.
  const outgoing = animating ? (direction > 0 ? prev : spreadIdx) : spreadIdx;
  const incoming = animating ? (direction > 0 ? spreadIdx : prev) : null;

  return (
    <div className="pages-viewport">
      <div
        key={`${outgoing}-${incoming}`}
        className="pages-track"
        style={{
          animation: animating
            ? `slide-${direction > 0 ? "next" : "prev"} 0.55s var(--ease-page) both`
            : "none",
        }}
      >
        <Frame chapter={chapter} items={items} idx={outgoing} />
        {incoming != null && <Frame chapter={chapter} items={items} idx={incoming} />}
      </div>
    </div>
  );
}

/* ============================================================
   COMMAND PALETTE (Ctrl+K) — busca unificada via API semântica
   ============================================================ */

function CmdK({ open, onClose, onPick }) {
  const [q, setQ] = useState("");
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const inputRef = useRef();
  const debounceRef = useRef(null);

  useEffect(() => {
    if (open) setTimeout(() => inputRef.current?.focus(), 50);
  }, [open]);

  useEffect(() => {
    if (!open) { setQ(""); setResults([]); }
  }, [open]);

  useEffect(() => {
    clearTimeout(debounceRef.current);
    if (!q.trim()) { setResults([]); return; }
    setLoading(true);
    debounceRef.current = setTimeout(async () => {
      const r = await unifiedSearch(q, 12);
      setResults(r);
      setLoading(false);
    }, 280);
    return () => clearTimeout(debounceRef.current);
  }, [q]);

  if (!open) return null;
  return (
    <div className="cmdk-backdrop" onClick={onClose}>
      <div className="cmdk" onClick={(e) => e.stopPropagation()}>
        <input
          ref={inputRef}
          className="cmdk-input"
          placeholder="O que procuras, escriba?"
          value={q}
          onChange={(e) => setQ(e.target.value)}
          onKeyDown={(e) => { if (e.key === "Escape") onClose(); }}
        />
        <div className="cmdk-list">
          {q.trim() === "" && (
            <div
              style={{
                padding: 20,
                fontStyle: "italic",
                color: "var(--ink-2)",
                textAlign: "center",
                fontSize: 13,
              }}
            >
              Digite o nome de uma magia, poder, condição… ou descreva o efeito.
            </div>
          )}
          {loading && (
            <div style={{ padding: 16, textAlign: "center", color: "var(--ink-2)", fontSize: 13 }}>
              Consultando o oráculo…
            </div>
          )}
          {!loading && results.map(({ item, chapter }) => (
            <div
              key={chapter.id + "-" + item.id}
              className="cmdk-row"
              style={{ color: chapter.color }}
              onClick={() => { onPick(chapter, item); onClose(); }}
            >
              <span className="kind-dot" />
              <div>
                <div className="row-title" style={{ color: "var(--ink-0)" }}>
                  {item.name}
                </div>
                <div style={{ fontSize: 12, color: "var(--ink-2)", fontStyle: "italic" }}>
                  {item.description && item.description.slice(0, 80) + "…"}
                </div>
              </div>
              <div className="row-meta">{chapter.label}</div>
            </div>
          ))}
          {!loading && q.trim() !== "" && results.length === 0 && (
            <div
              style={{
                padding: 30,
                fontStyle: "italic",
                color: "var(--ink-2)",
                textAlign: "center",
              }}
            >
              O oráculo nada vê.
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   HISTORY DRAWER
   ============================================================ */

function HistoryDrawer({ open, onClose, history, onPick }) {
  return (
    <div className="history-drawer" data-open={open}>
      <div
        style={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "baseline",
        }}
      >
        <h3>Páginas Visitadas</h3>
        <button
          onClick={onClose}
          style={{
            background: "none",
            border: 0,
            color: "var(--gilt-2)",
            cursor: "pointer",
            fontSize: 18,
          }}
        >
          ×
        </button>
      </div>
      {history.length === 0 && (
        <p style={{ fontStyle: "italic", color: "var(--gilt-2)", fontSize: 13 }}>
          Nenhuma página visitada ainda.
        </p>
      )}
      {history.map((h, i) => (
        <div key={i} className="history-row" onClick={() => onPick(h)}>
          {h.name}
          <span className="row-meta">{h.chapterLabel}</span>
        </div>
      ))}
    </div>
  );
}

/* ============================================================
   LOADING SCREEN
   ============================================================ */

function LoadingScreen({ error }) {
  return (
    <div
      style={{
        minHeight: "100vh",
        display: "grid",
        placeItems: "center",
        background: "var(--cover-0)",
        color: "var(--gilt-1)",
        fontFamily: "var(--font-display)",
        textAlign: "center",
      }}
    >
      <div>
        <div style={{ fontSize: 48, marginBottom: 16 }}>❦</div>
        {error ? (
          <>
            <div style={{ fontSize: 18, color: "var(--kw-condition)", marginBottom: 8 }}>
              Falha ao conectar com o oráculo
            </div>
            <div style={{ fontSize: 13, color: "var(--gilt-2)", fontFamily: "var(--font-mono)" }}>
              {error}
            </div>
            <div style={{ fontSize: 13, color: "var(--gilt-2)", marginTop: 12, fontFamily: "var(--font-body)", fontStyle: "italic" }}>
              Verifique se o servidor está rodando em {API_BASE}
            </div>
          </>
        ) : (
          <div style={{ fontSize: 15, letterSpacing: "0.14em", textTransform: "uppercase" }}>
            Abrindo o grimório…
          </div>
        )}
      </div>
    </div>
  );
}

/* ============================================================
   ROOT APP
   ============================================================ */

const DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "light",
  "animation": "slide",
  "ornaments": "medium",
  "fontDisplay": "Cinzel",
  "fontBody": "EB Garamond"
}/*EDITMODE-END*/;

function App() {
  const [tweaks, setTweak] = useTweaks(DEFAULTS);
  const [ready, setReady] = useState(false);
  const [loadError, setLoadError] = useState(null);
  const [chapters, setChapters] = useState([]);

  const [chapterId, setChapterId] = useState(() => lsGet("arton_chapter") || "magias");
  const [filters, setFilters] = useState(() => lsGet(`arton_filters_${lsGet("arton_chapter") || "magias"}`) || {});
  const [query, setQuery] = useState("");
  const [spreadIdx, setSpreadIdx] = useState(() => lsGet(`arton_spread_${lsGet("arton_chapter") || "magias"}`) || 0);
  const [cmdkOpen, setCmdkOpen] = useState(false);
  const [historyOpen, setHistoryOpen] = useState(false);
  const [history, setHistory] = useState([]);
  const [importOpen, setImportOpen] = useState(false);
  const searchInputRef = useRef();

  // wait for DATA_READY promise
  useEffect(() => {
    window.DATA_READY.then(() => {
      setChapters(window.CHAPTERS || []);
      setReady(true);
    }).catch((err) => {
      setLoadError(err.message || String(err));
    });
  }, []);

  const chapter = useMemo(
    () => chapters.find((c) => c.id === chapterId) || chapters[0] || { id: "", label: "", data: [], filterGroups: [], disabled: false },
    [chapters, chapterId]
  );

  useEffect(() => {
    lsSet("arton_chapter", chapterId);
    setSpreadIdx(lsGet(`arton_spread_${chapterId}`) || 0);
    setFilters(lsGet(`arton_filters_${chapterId}`) || {});
  }, [chapterId]);

  useEffect(() => { lsSet(`arton_spread_${chapterId}`, spreadIdx); }, [spreadIdx, chapterId]);
  useEffect(() => { lsSet(`arton_filters_${chapterId}`, filters); }, [filters, chapterId]);

  const filtersWithDefs = useMemo(() => {
    const defs = {};
    for (const g of chapter.filterGroups || []) defs[g.id] = g;
    return { ...filters, _defs: defs };
  }, [filters, chapter]);

  const items = useMemo(
    () => applyFilters(Array.isArray(chapter.data) ? chapter.data : [], filtersWithDefs, query),
    [chapter, filtersWithDefs, query]
  );
  const totalSpreads = Math.max(1, Math.ceil(items.length / 2));

  const counts = useMemo(() => {
    const r = {};
    const data = Array.isArray(chapter.data) ? chapter.data : [];
    for (const g of chapter.filterGroups || []) {
      r[g.id] = {};
      for (const v of g.values) {
        r[g.id][v] = data.filter((it) => g.match(it, v)).length;
      }
    }
    return r;
  }, [chapter]);

  const next = useCallback(
    () => setSpreadIdx((i) => Math.min(i + 1, totalSpreads - 1)),
    [totalSpreads]
  );
  const prev = useCallback(() => setSpreadIdx((i) => Math.max(i - 1, 0)), []);

  useEffect(() => {
    const item = items[spreadIdx * 2];
    if (!item) return;
    setHistory((h) => {
      const next = [
        { chapterId: chapter.id, chapterLabel: chapter.label, itemId: item.id, name: item.name },
        ...h.filter((x) => x.itemId !== item.id),
      ].slice(0, 12);
      return next;
    });
  }, [spreadIdx, items, chapter]);

  useEffect(() => {
    const onKey = (e) => {
      if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA") {
        if (e.key === "Escape") e.target.blur();
        return;
      }
      if (e.key === "ArrowRight") next();
      else if (e.key === "ArrowLeft") prev();
      else if (e.key === "/") {
        e.preventDefault();
        searchInputRef.current?.focus();
      } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
        e.preventDefault();
        setCmdkOpen(true);
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [next, prev]);

  useEffect(() => {
    document.documentElement.setAttribute("data-theme", tweaks.theme);
    document.documentElement.style.setProperty(
      "--font-display",
      `"${tweaks.fontDisplay}", serif`
    );
    document.documentElement.style.setProperty(
      "--font-body",
      `"${tweaks.fontBody}", serif`
    );
  }, [tweaks.theme, tweaks.fontDisplay, tweaks.fontBody]);

  const handlePickResult = (ch, item) => {
    setChapterId(ch.id);
    setFilters({});
    setQuery("");
    setTimeout(() => {
      const idx = (Array.isArray(ch.data) ? ch.data : []).findIndex((x) => x.id === item.id);
      if (idx >= 0) setSpreadIdx(Math.floor(idx / 2));
    }, 50);
  };

  const handleHistoryPick = (h) => {
    const ch = chapters.find((c) => c.id === h.chapterId);
    if (!ch) return;
    const item = (ch.data || []).find((x) => x.id === h.itemId);
    if (!item) return;
    handlePickResult(ch, item);
    setHistoryOpen(false);
  };

  if (!ready) return <LoadingScreen error={loadError} />;

  return (
    <div className="app">
      {/* Top bar */}
      <header className="topbar">
        <div className="brand">
          <span className="sigil">A</span>
          <div>
            ESCRIBA DE ARTON
            <small>guia para escribas e errantes</small>
          </div>
        </div>
        <div className="search">
          <svg
            className="search-icon"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth="1.5"
          >
            <circle cx="10.5" cy="10.5" r="6.5" />
            <path d="m20 20-5-5" />
          </svg>
          <input
            ref={searchInputRef}
            placeholder={`Procurar em ${chapter.label ? chapter.label.toLowerCase() : "…"}…`}
            value={query}
            onChange={(e) => { setQuery(e.target.value); setSpreadIdx(0); }}
            onKeyDown={(e) => { if (e.key === "Escape") e.target.blur(); }}
          />
          <span className="kbd">/</span>
          <span className="kbd" style={{ marginLeft: 4 }}>Ctrl+K</span>
        </div>
        <div className="actions">
          <button
            className="icon-btn"
            title="Oráculo (Ctrl+K)"
            onClick={() => setCmdkOpen(true)}
          >
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
              <path d="M2 12s3.5-6.5 10-6.5S22 12 22 12s-3.5 6.5-10 6.5S2 12 2 12Z" />
              <circle cx="12" cy="12" r="3" />
              <circle cx="12" cy="12" r="1" fill="currentColor" />
            </svg>
          </button>
          <button
            className="icon-btn"
            title="Histórico"
            onClick={() => setHistoryOpen((o) => !o)}
          >
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
              <circle cx="12" cy="12" r="9" />
              <path d="M12 7v5l3 2" />
            </svg>
          </button>
          <button
            className="icon-btn"
            title="Alternar tema"
            onClick={() =>
              setTweak("theme", tweaks.theme === "light" ? "dark" : "light")
            }
          >
            {tweaks.theme === "light" ? (
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
                <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z" />
              </svg>
            ) : (
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
                <circle cx="12" cy="12" r="4" />
                <path d="M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.4 1.4M17.6 17.6 19 19M5 19l1.4-1.4M17.6 6.4 19 5" />
              </svg>
            )}
          </button>
        </div>
      </header>

      {/* Book */}
      <div className="book-wrap">
        <div className="book">
          <BookIndex
            chapter={chapter}
            filters={filters}
            setFilters={setFilters}
            counts={counts}
          />

          <div className="spread">
            {chapter.id === "classes"
              ? <window.ClassCreator/>
              : chapter.id === "racas"
              ? <window.RaceCreator/>
              : chapter.id === "origens"
              ? <window.OriginCreator/>
              : chapter.id === "divindade"
              ? <window.DeityCreator/>
              : chapter.id === "pericias"
              ? <window.SkillsCreator/>
              : chapter.id === "ficha"
              ? <window.SheetCreator/>
              : <Spread
                  chapter={chapter}
                  items={items}
                  spreadIdx={spreadIdx}
                  totalSpreads={totalSpreads}
                  animation={tweaks.animation}
                />
            }
          </div>

          <BookTabs
            chapterId={chapterId}
            setChapterId={setChapterId}
            chapters={chapters}
          />
        </div>
      </div>

      {/* Footer — hidden on creator tabs */}
      <footer className="book-footer" style={{ visibility: ["classes", "racas", "origens", "divindade", "pericias", "ficha"].includes(chapter.id) ? "hidden" : "visible" }}>
        <button
          className="flip-btn"
          disabled={spreadIdx === 0}
          onClick={prev}
          aria-label="Página anterior"
        >
          ‹
        </button>
        <div className="folio">
          {items.length === 0
            ? "Nenhum resultado"
            : `Fólio ${spreadIdx * 2 + 1}${
                items[spreadIdx * 2 + 1] ? "–" + (spreadIdx * 2 + 2) : ""
              } de ${items.length}`}
        </div>
        <button
          className="flip-btn"
          disabled={spreadIdx >= totalSpreads - 1}
          onClick={next}
          aria-label="Próxima página"
        >
          ›
        </button>
      </footer>

      <CmdK
        open={cmdkOpen}
        onClose={() => setCmdkOpen(false)}
        onPick={handlePickResult}
      />
      <HistoryDrawer
        open={historyOpen}
        onClose={() => setHistoryOpen(false)}
        history={history}
        onPick={handleHistoryPick}
      />

      <TweaksPanel title="Tweaks">
        <TweakSection title="Tema & Tipografia">
          <TweakRadio
            label="Tema"
            value={tweaks.theme}
            options={[
              { value: "light", label: "Claro" },
              { value: "dark", label: "Escuro" },
            ]}
            onChange={(v) => setTweak("theme", v)}
          />
          <TweakSelect
            label="Fonte títulos"
            value={tweaks.fontDisplay}
            options={["Cinzel", "IM Fell English SC", "Cormorant SC", "UnifrakturMaguntia"]}
            onChange={(v) => setTweak("fontDisplay", v)}
          />
          <TweakSelect
            label="Fonte corpo"
            value={tweaks.fontBody}
            options={["EB Garamond", "Cormorant Garamond", "Crimson Pro", "Vollkorn"]}
            onChange={(v) => setTweak("fontBody", v)}
          />
        </TweakSection>
        <TweakSection title="Animação">
          <TweakRadio
            label="Virar página"
            value={tweaks.animation}
            options={[
              { value: "slide", label: "Slide" },
              { value: "fade", label: "Fade" },
            ]}
            onChange={(v) => setTweak("animation", v)}
          />
        </TweakSection>
        <TweakSection title="Ornamentação">
          <TweakRadio
            label="Nível"
            value={tweaks.ornaments}
            options={[
              { value: "low", label: "Discreto" },
              { value: "medium", label: "Médio" },
              { value: "high", label: "Pesado" },
            ]}
            onChange={(v) => setTweak("ornaments", v)}
          />
        </TweakSection>
        <TweakSection title="Ficha">
          <button
            className="twk-import-btn"
            onClick={() => setImportOpen(true)}
            style={{
              width: "100%", padding: "8px 12px",
              background: "transparent", color: "var(--ink-0, #2a1f0e)",
              border: "1px solid var(--ink-rule, #c9b88a)",
              borderRadius: 3, cursor: "pointer",
              fontSize: 12, letterSpacing: "0.12em", textTransform: "uppercase",
              fontFamily: "inherit",
            }}
          >
            Importar ficha…
          </button>
        </TweakSection>
      </TweaksPanel>
      {window.ImportSheetModal && (
        <window.ImportSheetModal open={importOpen} onClose={() => setImportOpen(false)} />
      )}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);

const _attrPanelRoot = document.getElementById("attr-panel-root");
if (_attrPanelRoot && window.AttrPanel) {
  ReactDOM.createRoot(_attrPanelRoot).render(<window.AttrPanel />);
}
