/* global React, window, Kw, Parsed */
/* ============================================================
   CLASS CREATOR — aba "Classes" do livro.

   Substitui <Spread> quando chapter.id === "classes".
   Esquerda = config (classe + nível + info)
   Direita  = picks de magia, nível a nível

   Regra crítica: budget_by_level vem da API. Picks NÃO acumulam.
   Cada slot só permite os available_circles do seu nível.
   ============================================================ */

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

/* ----- helper: debounce ----- */
/* ------------------------------------------------------------
   Pré-requisitos de atributo
   ------------------------------------------------------------ */
function useAttrTotals() {
  const [totals, setTotals] = useState(
    () => (window.CHAR_ATTRS && window.CHAR_ATTRS.getState().totals) || {}
  );
  useEffect(() => {
    if (!window.CHAR_ATTRS) return undefined;
    return window.CHAR_ATTRS.subscribe((s) => setTotals(s.totals));
  }, []);
  return totals;
}

function meetsAttrPrereqs(power, totals) {
  const parsed = power?.prerequisites_parsed?.attrs;
  if (!parsed || Object.keys(parsed).length === 0) return { ok: true, missing: [] };
  const missing = [];
  for (const [attr, min] of Object.entries(parsed)) {
    if ((totals?.[attr] ?? -99) < min) missing.push({ attr, min });
  }
  return { ok: missing.length === 0, missing };
}

function fmtPrereqLabel(missing) {
  return missing
    .map((m) => `${m.attr} ${m.min >= 0 ? "+" : ""}${m.min}`)
    .join(", ");
}

function useDebounced(value, ms) {
  const [v, setV] = useState(value);
  useEffect(() => {
    const t = setTimeout(() => setV(value), ms);
    return () => clearTimeout(t);
  }, [value, ms]);
  return v;
}

/* ----- helper: extrai o nome da divindade do nome de um Poder Concedido -----
   Dois formatos suportados:
     "Nome do Poder (Deus)"   → "Deus"
     "Escolhido de Deus"      → "Deus"
   Retorna null se o formato não bater.
*/
function getPowerDeity(powerName) {
  if (!powerName) return null;
  const m1 = powerName.match(/^Escolhido\s+de\s+(.+?)\s*$/i);
  if (m1) return m1[1].trim();
  const m2 = powerName.match(/\(([^)]+)\)\s*$/);
  if (m2) return m2[1].trim();
  return null;
}

/* ----- helper: tag entre parênteses no fim do nome do poder ----- */
function getParenTag(powerName) {
  if (!powerName) return null;
  const m = powerName.match(/\(([^)]+)\)\s*$/);
  return m ? m[1].trim() : null;
}

/* ----- helper: normaliza string para comparação case- e accent-insensitive ----- */
function normTxt(s) {
  if (!s) return "";
  return s.trim().toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "");
}

/* ----- helper: tag de raça do poder bate com a raça salva? -----
   Formatos suportados na tag:
     "Anão"                        → nome simples
     "Anão, Goblin, Hobgoblin"     → vírgula = OU
     "Sereia/Tritão"               → barra = OU
     "Suraggel - Aggelus"          → hífen = name + variant
     "Moreau do Lobo"              → preposição = name + variant
     "Qareen da Luz"               → idem
     "Naidora[Elfo-do-Céu]"        → colchetes são notas, ignorar
     "Várias"                      → wildcard, aceita qualquer raça
   Para Moreau, "do Lobo" precisa bater com variant "Herança do Lobo" —
   por isso comparamos com `.includes`.
*/
function raceTagMatchesPart(part, savedRace) {
  let clean = part.replace(/\s*\[[^\]]*\]/g, "").trim();
  if (!clean) return false;

  /* "Sereia/Tritão" — OU entre alternativas */
  if (clean.includes("/")) {
    return clean.split("/").some((s) => raceTagMatchesPart(s.trim(), savedRace));
  }

  /* "Suraggel - Aggelus" (hífen) ou "Moreau do Lobo" (preposição) */
  const hyphenMatch = clean.match(/^(.+?)\s+-\s+(.+)$/);
  const prepMatch   = clean.match(/^(.+?)\s+d[oae]s?\s+(.+)$/i);
  const compound    = hyphenMatch || prepMatch;

  if (compound) {
    const [, name, tail] = compound;
    if (normTxt(name) !== normTxt(savedRace.name)) return false;
    /* a cauda precisa estar contida na variant salva
       (ex: "Lobo" ⊂ "Herança do Lobo") */
    return !!savedRace.variant && normTxt(savedRace.variant).includes(normTxt(tail));
  }

  /* nome simples */
  return normTxt(clean) === normTxt(savedRace.name);
}

function raceTagMatches(tag, savedRace) {
  if (!tag || !savedRace?.name) return false;
  if (/^v[áa]rias$/i.test(tag.trim())) return true; /* "Várias" = wildcard */
  return tag.split(/\s*,\s*/).some((part) => raceTagMatchesPart(part, savedRace));
}

/* ----- helper: filtra poderes restritos (concedido + de raça) -----
   - "Poder Concedido" → só para classes is_devoted + divindade salva
   - "Poder de Raça"   → só para a raça correspondente
   Poderes sem tag entre parênteses ficam visíveis (fallback seguro).
*/
/* ----- helper: bate uma regra restricted_to contra o estado salvo ----- */
function ruleMatches(rule, ctx) {
  if (!rule) return true;

  /* OR composto */
  if (Array.isArray(rule.any_of)) {
    return rule.any_of.some((sub) => ruleMatches(sub, ctx));
  }

  /* raça */
  if (rule.race) {
    if (!ctx.savedRace?.name) return false;
    if (normTxt(rule.race) !== normTxt(ctx.savedRace.name)) return false;
  }

  /* classe + path + lineage (qualquer entry de classe deve casar) */
  if (rule.class || rule.path || rule.lineage) {
    const match = ctx.classEntries.some((e) => {
      if (rule.class && normTxt(e.className) !== normTxt(rule.class)) return false;
      if (rule.path && normTxt(e.path) !== normTxt(rule.path)) return false;
      if (rule.lineage && normTxt(e.lineage) !== normTxt(rule.lineage)) return false;
      return true;
    });
    if (!match) return false;
  }
  return true;
}

function filterRestrictedPowers(powers, classData) {
  if (!Array.isArray(powers)) return powers;
  const isDevoted = !!classData?.is_devoted;

  let savedDeityName = null;
  let savedRace = null;
  let classEntries = [];
  try {
    if (isDevoted) {
      const d = JSON.parse(localStorage.getItem("arton_char_deity"));
      savedDeityName = d?.name || null;
    }
    savedRace = JSON.parse(localStorage.getItem("arton_char_race"));
    const charData = JSON.parse(localStorage.getItem("arton_char_class"));
    classEntries = Array.isArray(charData?.classes) ? charData.classes : [];
  } catch {}

  const ctx = { savedRace, classEntries };

  /* feiticeiro + linhagem dracônica? — usado para Poder Dracônico */
  const hasDraconicSorcerer = classEntries.some((e) =>
    normTxt(e.className) === normTxt("Arcanista") &&
    normTxt(e.path) === normTxt("Feiticeiro") &&
    normTxt(e.lineage) === normTxt("Dracônica")
  );

  return powers.filter((p) => {
    /* restrição explícita (overrides) — fonte de verdade */
    if (p.restricted_to && !ruleMatches(p.restricted_to, ctx)) return false;

    if (p.power_type === "Poder Concedido") {
      if (!isDevoted || !savedDeityName) return false;
      const fromName = getPowerDeity(p.name);
      if (!fromName) return true;
      const deities = fromName.split(/,\s*/).map((s) => s.trim());
      return deities.includes(savedDeityName);
    }
    if (p.power_type === "Poder de Raça") {
      if (!savedRace?.name) return false;
      const tag = getParenTag(p.name);
      if (!tag) return true;
      return raceTagMatches(tag, savedRace);
    }
    if (p.power_type === "Poder Dracônico") {
      /* Coração de Dragão: Kallyanach (raça) OU Feiticeiro Dracônico */
      if (savedRace?.name && normTxt(savedRace.name) === normTxt("Kallyanach")) return true;
      if (hasDraconicSorcerer) return true;
      return false;
    }
    return true;
  });
}

/* alias mantido para retrocompatibilidade com chamadas existentes */
const filterGrantedPowers = filterRestrictedPowers;

/* ----- helper: corner brackets (mesmo do app principal) ----- */
function CreatorCorners() {
  return <>
    <span className="frame-corner tl"/>
    <span className="frame-corner tr"/>
    <span className="frame-corner bl"/>
    <span className="frame-corner br"/>
  </>;
}

/* ============================================================
   DUPLO FEÉRICO BANNER — alerta cruzado origem ↔ classe
   Aceita array de class names (multiclasse).
   ============================================================ */
function DuploFeericoBanner({ classNames }) {
  const origin = (() => { try { return JSON.parse(localStorage.getItem("arton_char_origin")); } catch { return null; } })();
  const duplo  = origin?.duplo_pick;

  /* só aparece quando há um Duplo Feérico salvo */
  if (!duplo?.class_name) return null;

  const names = (classNames || []).filter(Boolean);
  const isConflict = names.includes(duplo.class_name);

  return (
    <div className={"cc-duplo-banner" + (isConflict ? " is-conflict" : "")}>
      <div className="cc-duplo-banner-icon">{isConflict ? "⚠" : "✦"}</div>
      <div className="cc-duplo-banner-body">
        <div className="cc-duplo-banner-title">
          {isConflict ? "Conflito com Duplo Feérico" : "Duplo Feérico ativo"}
        </div>
        <div className="cc-duplo-banner-text">
          Sua origem <em>{origin.name}</em> escolheu a habilidade{" "}
          <strong>{duplo.feature_name}</strong> de <Kw kind="class">{duplo.class_name}</Kw>.
          {isConflict ? (
            <>
              {" "}Você <strong>não pode</strong> ter <Kw kind="class">{duplo.class_name}</Kw>{" "}
              entre suas classes, pois é a mesma do Duplo.
              {" "}Remova-a — ou volte à aba <em>Origens</em> e ajuste a habilidade duplicada.
            </>
          ) : (
            <>
              {" "}Lembre-se: nenhuma de suas classes pode ser <Kw kind="class">{duplo.class_name}</Kw>.
            </>
          )}
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   helper: atributo de conjuração efetivo (path override) e
   sub-escolha encadeada do path_metadata
   ============================================================ */

function getEffectiveCastingAttribute(classData, entry) {
  const meta = classData?.path_metadata;
  if (!meta || !entry?.path) return classData?.casting_attribute;
  const opt = (meta.options || []).find((o) => o.id === entry.path);
  return opt?.casting_attribute || classData?.casting_attribute;
}

function getSubChoice(classData, entry) {
  const meta = classData?.path_metadata;
  if (!meta || !entry?.path) return null;
  const opt = (meta.options || []).find((o) => o.id === entry.path);
  return opt?.sub_choice || null;
}

/* ============================================================
   CLASS CARD (uma classe na lista — multiclasse)
   ============================================================ */
function ClassCard({
  entry, index, isActive,
  onActivate, onRemove, onChangeClass, onChangeLevel,
  onChangePath, onChangeLineage, onToggleMulti,
  availableClassNames, allClasses,
}) {
  const classData = entry.className
    ? allClasses.find((c) => c.name === entry.className)
    : null;

  const options = entry.className && !availableClassNames.includes(entry.className)
    ? [entry.className, ...availableClassNames].sort((a, b) => a.localeCompare(b, "pt-BR"))
    : availableClassNames.slice().sort((a, b) => a.localeCompare(b, "pt-BR"));

  const pathMeta = classData?.path_metadata || null;
  const subChoice = getSubChoice(classData, entry);
  const effectiveAttr = getEffectiveCastingAttribute(classData, entry);

  return (
    <div
      className={"cc-class-card" + (isActive ? " is-active" : "") + (!entry.className ? " is-empty" : "")}
      onClick={onActivate}
    >
      <div className="cc-card-head">
        <span className="cc-card-index">{index + 1}</span>
        <div className="cc-select-wrap cc-card-select">
          <select
            className="cc-select"
            value={entry.className || ""}
            onClick={(e) => e.stopPropagation()}
            onChange={(e) => onChangeClass(e.target.value || null)}
          >
            <option value="">— escolha uma classe —</option>
            {options.map((name) => (
              <option key={name} value={name}>{name}</option>
            ))}
          </select>
          <span className="cc-select-chevron">▾</span>
        </div>
        <button
          className="cc-card-remove"
          onClick={(e) => { e.stopPropagation(); onRemove(); }}
          aria-label="Remover classe"
          title="Remover classe"
        >×</button>
      </div>

      {entry.className && (
        <div className="cc-card-level" onClick={(e) => e.stopPropagation()}>
          <button onClick={() => onChangeLevel(Math.max(1, entry.level - 1))} aria-label="Diminuir nível">−</button>
          <div className="cc-card-level-display">
            <span className="cc-card-level-number">{entry.level}</span>
            <span className="cc-card-level-label">nível</span>
          </div>
          <button onClick={() => onChangeLevel(Math.min(20, entry.level + 1))} aria-label="Aumentar nível">+</button>
        </div>
      )}

      {/* === Sub-picker da escolha de classe (path/multi) === */}
      {pathMeta && (() => {
        const unlockLevel = pathMeta.unlock_level || 1;
        const locked = entry.level < unlockLevel;
        return (
        <div className={"cc-card-pathpicker" + (locked ? " is-locked" : "")} onClick={(e) => e.stopPropagation()}>
          <label className="cc-card-path-label">
            {pathMeta.feature_name}
            {locked && (
              <span style={{
                fontFamily: "var(--font-body)",
                fontSize: 10,
                fontStyle: "italic",
                color: "var(--ink-2)",
                textTransform: "none",
                letterSpacing: 0,
                marginLeft: 6,
              }}>
                disponível no nível {unlockLevel}
              </span>
            )}
            {pathMeta.type === "multi" && (() => {
              const cur = (entry[pathMeta.save_key] || []).length;
              const max = pathMeta.max || 0;
              return (
                <span style={{
                  fontFamily: "var(--font-body)",
                  fontSize: 10,
                  fontStyle: "italic",
                  color: cur === max ? "var(--gilt-1)" : "var(--kw-condition)",
                  textTransform: "none",
                  letterSpacing: 0,
                  marginLeft: 6,
                }}>
                  {cur}/{max}
                </span>
              );
            })()}
          </label>
          <div className="cc-card-path-chips">
            {(pathMeta.options || []).map((opt) => {
              if (pathMeta.type === "multi") {
                const selected = (entry[pathMeta.save_key] || []).includes(opt.id);
                const full = !selected && (entry[pathMeta.save_key] || []).length >= (pathMeta.max || Infinity);
                const disabled = locked || full;
                return (
                  <button
                    key={opt.id}
                    className={"cc-path-chip" + (selected ? " active" : "")}
                    disabled={disabled}
                    style={disabled ? { opacity: 0.45, cursor: "not-allowed" } : null}
                    onClick={() => !locked && onToggleMulti(pathMeta.save_key, opt.id, pathMeta.max)}
                    title={locked ? `Disponível no nível ${unlockLevel}` : (opt.summary || "")}
                  >
                    {opt.id}
                  </button>
                );
              }
              return (
                <button
                  key={opt.id}
                  className={"cc-path-chip" + (entry.path === opt.id ? " active" : "")}
                  disabled={locked}
                  style={locked ? { opacity: 0.45, cursor: "not-allowed" } : null}
                  onClick={() => !locked && onChangePath(opt.id)}
                  title={locked ? `Disponível no nível ${unlockLevel}` : (opt.summary || "")}
                >
                  {opt.id}
                </button>
              );
            })}
          </div>
        </div>
        );
      })()}

      {/* === Sub-picker de linhagem (encadeado) === */}
      {subChoice && (() => {
        const subUnlock = subChoice.unlock_level || 1;
        const subLocked = entry.level < subUnlock;
        return (
        <div className={"cc-card-pathpicker" + (subLocked ? " is-locked" : "")} onClick={(e) => e.stopPropagation()}>
          <label className="cc-card-path-label">
            {subChoice.feature_name}
            {subLocked && (
              <span style={{
                fontFamily: "var(--font-body)",
                fontSize: 10,
                fontStyle: "italic",
                color: "var(--ink-2)",
                textTransform: "none",
                letterSpacing: 0,
                marginLeft: 6,
              }}>
                disponível no nível {subUnlock}
              </span>
            )}
          </label>
          <div className="cc-card-path-chips">
            {(subChoice.options || []).map((opt) => (
              <button
                key={opt.id}
                className={"cc-path-chip" + (entry.lineage === opt.id ? " active" : "")}
                disabled={subLocked}
                style={subLocked ? { opacity: 0.45, cursor: "not-allowed" } : null}
                onClick={() => !subLocked && onChangeLineage(opt.id)}
                title={subLocked ? `Disponível no nível ${subUnlock}` : (opt.summary || "")}
              >
                {opt.id}
              </button>
            ))}
          </div>
        </div>
        );
      })()}

      {classData && (
        <div className="cc-card-info">
          <span title="Dado de PV">d{classData.hp_die}</span>
          <span title="Perícias">{classData.skill_points} per.</span>
          {classData.is_caster && (
            <span title="Atributo de conjuração" className="cc-card-info-caster">
              <Kw kind="attribute">{effectiveAttr}</Kw>
            </span>
          )}
        </div>
      )}
    </div>
  );
}

/* ============================================================
   CLASS LIST PANEL (página esquerda — multiclasse)
   ============================================================ */
function ClassListPanel({
  classes, allClasses, classEntries, activeIndex,
  setActiveIndex, onAdd, onRemove, onChangeClass, onChangeLevel,
  onChangePath, onChangeLineage, onToggleMulti,
  onOpenSummary,
}) {
  /* classes ainda disponíveis (não usadas em nenhum card) */
  const usedNames = new Set(classEntries.map((c) => c.className).filter(Boolean));
  const availableClassNames = allClasses
    .map((c) => c.name)
    .filter((n) => !usedNames.has(n));

  const totalLevel = classEntries.reduce(
    (sum, e) => sum + (e.className ? e.level : 0),
    0
  );

  const activeEntry = classEntries[activeIndex] || null;
  const activeData  = activeEntry?.className
    ? allClasses.find((c) => c.name === activeEntry.className)
    : null;

  /* nomes preenchidos para o banner */
  const filledNames = classEntries.map((c) => c.className).filter(Boolean);

  /* impedir adicionar quando não há mais classes ou último card ainda vazio */
  const lastEmpty   = classEntries.length > 0 && !classEntries[classEntries.length - 1].className;
  const canAdd      = availableClassNames.length > 0 && !lastEmpty;

  return (
    <div className="page-content cc-config">
      <header className="page-header">
        <span className="page-eyebrow">Classes · Personagem</span>
        <span className="page-folio">
          {classEntries.length === 0 ? "Ficha" : `Nível ${totalLevel}`}
        </span>
      </header>

      <h1 className="page-title">Criar Personagem</h1>
      <p className="page-subtitle">
        Adicione uma ou mais <Kw kind="class">classes</Kw>. Cada classe tem seu próprio nível.
      </p>

      <DuploFeericoBanner classNames={filledNames}/>

      <div className="cc-class-cards">
        {classEntries.map((entry, i) => (
          <ClassCard
            key={i}
            entry={entry}
            index={i}
            isActive={i === activeIndex}
            onActivate={() => setActiveIndex(i)}
            onRemove={() => onRemove(i)}
            onChangeClass={(name) => onChangeClass(i, name)}
            onChangeLevel={(lvl) => onChangeLevel(i, lvl)}
            onChangePath={(p) => onChangePath(i, p)}
            onChangeLineage={(l) => onChangeLineage(i, l)}
            onToggleMulti={(saveKey, id, max) => onToggleMulti(i, saveKey, id, max)}
            availableClassNames={availableClassNames}
            allClasses={allClasses}
          />
        ))}

        <button
          className="cc-add-class-btn"
          onClick={onAdd}
          disabled={!canAdd}
          title={
            !canAdd
              ? availableClassNames.length === 0
                ? "Todas as classes já foram adicionadas"
                : "Termine a classe atual primeiro"
              : "Adicionar classe"
          }
        >
          {classEntries.length === 0
            ? "+ Adicionar classe"
            : "+ Multiclasse"}
        </button>
      </div>

      {activeData && (
        <>
          <div className="divider">❦</div>
          <div className="cc-active-info">
            <div className="rc-section-label" style={{ marginBottom: 6 }}>
              Classe ativa: {activeData.name}
            </div>
            {activeData.description && (
              <p className="cc-class-desc">{activeData.description}</p>
            )}
            <dl className="statblock cc-class-info" style={{ marginTop: 8 }}>
              <div style={{ gridColumn: "1 / -1" }}>
                <dt>Resistências boas</dt>
                <dd>{(activeData.good_saves || []).join(" · ") || "—"}</dd>
              </div>
            </dl>
            {activeData.is_caster && (
              <button className="cc-summary-btn" onClick={onOpenSummary}>
                Ver sumário de magias
              </button>
            )}
          </div>
        </>
      )}
    </div>
  );
}

/* ============================================================
   PICKS PANEL (página direita)
   ============================================================ */

function PicksPanel({ classData, spellsData, loading, picks, setPick, onShowSpell, restrictedSchools }) {
  if (!classData) {
    return (
      <div className="page-content">
        <header className="page-header">
          <span className="page-eyebrow">Picks de Magia</span>
          <span className="page-folio">aguardando</span>
        </header>
        <div className="empty-state">
          <div className="ornament">✦ ✦ ✦</div>
          <div>Escolha uma classe à esquerda para começar.</div>
        </div>
      </div>
    );
  }

  if (!classData.is_caster) {
    return (
      <div className="page-content">
        <header className="page-header">
          <span className="page-eyebrow">Picks de Magia</span>
          <span className="page-folio">—</span>
        </header>
        <h2 className="page-title" style={{ fontSize: 24 }}>{classData.name}</h2>
        <p className="page-subtitle">Sem progressão de magias.</p>
        <div className="empty-state">
          <div className="ornament">⚔</div>
          <div>Esta classe não conjura magias.</div>
          <p style={{ fontSize: 13, marginTop: 12, maxWidth: 280, lineHeight: 1.5 }}>
            O poder de {classData.name} vem de outras fontes — combate, destreza,
            instinto ou liderança. Veja a aba de <em>Poderes</em>.
          </p>
        </div>
      </div>
    );
  }

  const budget = spellsData?.budget_by_level || [];
  /* total de slots de magia (picks pode variar por nível — Bardo 2 no L1) */
  const totalSlots = budget.reduce((sum, s) => sum + (s.picks || 0), 0);

  return (
    <div className="page-content cc-picks">
      <header className="page-header">
        <span className="page-eyebrow">Picks de Magia · {classData.name}</span>
        <span className="page-folio">
          {picks.filter(Boolean).length}/{totalSlots} escolhidas
        </span>
      </header>

      <h2 className="page-title" style={{ fontSize: 26 }}>Progressão</h2>
      <p className="page-subtitle">
        Cada slot só permite os círculos liberados no nível em que ele é aberto.
      </p>

      {loading && (
        <div className="cc-skeleton">
          {[...Array(3)].map((_, i) => <div key={i} className="cc-skeleton-row"/>)}
        </div>
      )}

      {!loading && totalSlots === 0 && (
        <div className="empty-state">
          <div className="ornament">·</div>
          <div>Ainda não há picks neste nível.</div>
        </div>
      )}

      {!loading && (() => {
        let slotIdx = 0;
        const rows = [];
        for (const slot of budget) {
          const count = slot.picks || 0;
          for (let i = 0; i < count; i++) {
            const idx = slotIdx++;
            rows.push(
              <PickRow
                key={`${slot.level}-${i}`}
                slot={slot}
                slotIndex={idx}
                slotSubIndex={count > 1 ? i + 1 : null}
                slotSubTotal={count > 1 ? count : null}
                spellsByCircle={spellsData.spells}
                currentPick={picks[idx]}
                allPicks={picks}
                onPick={(spell) => setPick(idx, spell)}
                onShowSpell={onShowSpell}
                restrictedSchools={restrictedSchools}
              />
            );
          }
        }
        return <div className="cc-pick-list">{rows}</div>;
      })()}
    </div>
  );
}

/* ----- single row: nível + seletor de magia ----- */
function PickRow({ slot, slotIndex, slotSubIndex, slotSubTotal, spellsByCircle, currentPick, allPicks, onPick, onShowSpell, restrictedSchools }) {
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState("");
  const rootRef = useRef();

  // fechar ao clicar fora
  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (!rootRef.current?.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);

  // pool de magias permitidas neste slot (filtra por available_circles + escolas)
  const allowed = useMemo(() => {
    const list = [];
    for (const c of slot.available_circles) {
      const arr = spellsByCircle[String(c)] || [];
      list.push(...arr);
    }
    let filteredBySchool = list;
    if (Array.isArray(restrictedSchools) && restrictedSchools.length > 0) {
      const schoolSet = new Set(restrictedSchools.map((s) => normTxt(s)));
      filteredBySchool = list.filter((s) => schoolSet.has(normTxt(s.school)));
    }
    filteredBySchool.sort((a, b) => a.circle - b.circle || a.name.localeCompare(b.name, "pt-BR"));
    return filteredBySchool;
  }, [slot.available_circles, spellsByCircle, restrictedSchools]);

  // já escolhidas em outros slots (não desabilita o pick deste mesmo slot)
  const otherPickIds = new Set(
    allPicks
      .filter((p, idx) => p && idx !== slotIndex)
      .map((p) => p.id)
  );

  const filtered = useMemo(() => {
    const q = query.toLowerCase().trim();
    const matchQ = (s) => !q || s.name.toLowerCase().includes(q);
    const usable = allowed.filter((s) => matchQ(s) && !otherPickIds.has(s.id));
    const used = allowed.filter((s) => matchQ(s) && otherPickIds.has(s.id));
    return { usable, used };
  }, [allowed, query, otherPickIds]);

  return (
    <div className="cc-pick-row" ref={rootRef}>
      <div className="cc-pick-level">
        <span className="cc-level-num">{slot.level}</span>
        <span className="cc-level-cap">
          {slotSubTotal ? `Nível · ${slotSubIndex}/${slotSubTotal}` : "Nível"}
        </span>
      </div>
      <div className="cc-pick-circles">
        {slot.available_circles.map((c) => (
          <span key={c} className="cc-circle-chip">
            <Kw kind="circle">{c}º círc.</Kw>
          </span>
        ))}
      </div>
      <div className="cc-pick-spell">
        {currentPick ? (
          <button className="cc-spell-chosen" onClick={() => setOpen((o) => !o)}>
            <div>
              <span className="cc-spell-name">{currentPick.name}</span>
              <span className="cc-spell-meta">
                <Kw kind="school">{currentPick.school}</Kw> · <Kw kind="circle">{currentPick.circle}º</Kw>
              </span>
            </div>
            <span className="cc-spell-actions">
              <span className="cc-spell-info" onClick={(e) => { e.stopPropagation(); onShowSpell(currentPick); }} title="Ver detalhes">ⓘ</span>
              <span className="cc-spell-clear" onClick={(e) => { e.stopPropagation(); onPick(null); }} title="Limpar">✕</span>
            </span>
          </button>
        ) : (
          <button className="cc-spell-empty" onClick={() => setOpen((o) => !o)}>
            <span>Escolher magia…</span>
            <span className="cc-chevron">▾</span>
          </button>
        )}

        {open && (
          <div className="cc-spell-dropdown">
            <input
              autoFocus
              className="cc-spell-search"
              placeholder="Buscar magia…"
              value={query}
              onChange={(e) => setQuery(e.target.value)}
            />
            <div className="cc-spell-options">
              {filtered.usable.map((s) => (
                <div
                  key={s.id}
                  className="cc-spell-opt"
                  onClick={() => { onPick(s); setOpen(false); setQuery(""); }}
                >
                  <span className="cc-opt-name">{s.name}</span>
                  <span className="cc-opt-meta">
                    <Kw kind="school">{s.school}</Kw>
                    <span className="cc-opt-circle">{s.circle}º</span>
                  </span>
                </div>
              ))}
              {filtered.usable.length === 0 && filtered.used.length === 0 && (
                <div className="cc-spell-empty-state">Nenhuma magia.</div>
              )}
              {filtered.used.length > 0 && (
                <>
                  <div className="cc-spell-divider">já escolhidas em outros níveis</div>
                  {filtered.used.map((s) => (
                    <div key={s.id} className="cc-spell-opt cc-disabled" title="Já escolhida em outro nível">
                      <span className="cc-opt-name">{s.name}</span>
                      <span className="cc-opt-meta">
                        <span style={{ opacity: 0.7 }}>{s.school}</span>
                        <span className="cc-opt-circle">{s.circle}º</span>
                      </span>
                    </div>
                  ))}
                </>
              )}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

/* ============================================================
   SUMMARY DRAWER
   ============================================================ */

function SummaryDrawer({ open, onClose, picks, classData, spellsData, onShowSpell }) {
  /* slot_index → nível em que o slot foi aberto (Bardo tem 2 no L1) */
  const slotToLevel = useMemo(() => {
    const out = [];
    for (const b of spellsData?.budget_by_level || []) {
      for (let i = 0; i < (b.picks || 0); i++) out.push(b.level);
    }
    return out;
  }, [spellsData]);

  const grouped = useMemo(() => {
    const out = {};
    for (let i = 0; i < picks.length; i++) {
      const s = picks[i];
      if (!s) continue;
      const k = s.circle;
      (out[k] = out[k] || []).push({ spell: s, level: slotToLevel[i] ?? i + 1 });
    }
    return out;
  }, [picks, slotToLevel]);

  const circles = Object.keys(grouped).map(Number).sort((a, b) => a - b);
  const total = picks.filter(Boolean).length;

  return (
    <div className={"cc-summary-drawer" + (open ? " open" : "")}>
      <header className="cc-summary-head">
        <div>
          <div className="cc-summary-eyebrow">Sumário · {classData?.name}</div>
          <div className="cc-summary-title">{total} {total === 1 ? "magia" : "magias"} escolhidas</div>
        </div>
        <button className="cc-summary-close" onClick={onClose} aria-label="Fechar">×</button>
      </header>
      <div className="cc-summary-body">
        {total === 0 && (
          <div className="empty-state" style={{ padding: 30 }}>
            <div className="ornament">·</div>
            <div>Nenhuma magia escolhida ainda.</div>
          </div>
        )}
        {circles.map((c) => (
          <section key={c} className="cc-summary-section">
            <h4>
              <Kw kind="circle">{c}º círculo</Kw>
              <span className="cc-summary-count">{grouped[c].length}</span>
            </h4>
            {grouped[c].map(({ spell, level }) => (
              <div key={spell.id + "-" + level} className="cc-summary-row" onClick={() => onShowSpell(spell)}>
                <span className="cc-summary-level">Nv {level}</span>
                <span className="cc-summary-name">{spell.name}</span>
                <span className="cc-summary-school">
                  <Kw kind="school">{spell.school}</Kw>
                </span>
              </div>
            ))}
          </section>
        ))}
      </div>
    </div>
  );
}

/* ============================================================
   SPELL DETAIL MODAL
   Real API fields: casting_time, saving_throw, description
   ============================================================ */

function SpellDetail({ spell, onClose }) {
  if (!spell) return null;
  const paras = (spell.description || "").split("\n\n").filter(Boolean);
  return (
    <div className="cc-detail-backdrop" onClick={onClose}>
      <div className="cc-detail" onClick={(e) => e.stopPropagation()}>
        <button className="cc-detail-close" onClick={onClose} aria-label="Fechar">×</button>
        <header className="page-header" style={{ marginBottom: 14 }}>
          <span className="page-eyebrow">Magia</span>
          <span className="page-folio">{spell.type}</span>
        </header>
        <h1 className="page-title">{spell.name}</h1>
        <p className="page-subtitle">
          <Kw kind="school">{spell.school}</Kw> · <Kw kind="circle">{spell.circle + "º círculo"}</Kw>
        </p>
        <dl className="statblock">
          {spell.casting_time && <div><dt>Execução</dt><dd><Parsed>{spell.casting_time}</Parsed></dd></div>}
          {spell.range       && <div><dt>Alcance</dt><dd>{spell.range}</dd></div>}
          {spell.target      && <div><dt>Alvo / Área</dt><dd>{spell.target}</dd></div>}
          {spell.duration    && <div><dt>Duração</dt><dd>{spell.duration}</dd></div>}
          {spell.saving_throw && (
            <div style={{ gridColumn: "1 / -1" }}>
              <dt>Resistência</dt><dd><Parsed>{spell.saving_throw}</Parsed></dd>
            </div>
          )}
        </dl>
        <div className="divider">❦</div>
        <div className="page-body" style={{ fontSize: 14 }}>
          {paras.map((p, i) => (
            <p key={i}><Parsed>{p}</Parsed></p>
          ))}
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   POWER PICKS PANEL
   ============================================================ */

function PowerPicksPanel({ classData, powersData, powerPicks, setPowerPick, onShowPower }) {
  if (!classData) {
    return (
      <div className="page-content">
        <header className="page-header">
          <span className="page-eyebrow">Poderes</span>
          <span className="page-folio">aguardando</span>
        </header>
        <div className="empty-state">
          <div className="ornament">✦ ✦ ✦</div>
          <div>Escolha uma classe à esquerda para começar.</div>
        </div>
      </div>
    );
  }

  const budget = powersData?.budget_by_level || [];
  const slotLevels = budget.filter((b) => b.power_slots > 0);
  const totalPicked = powerPicks.filter(Boolean).length;

  return (
    <div className="page-content cc-picks">
      <header className="page-header">
        <span className="page-eyebrow">Poderes · {classData.name}</span>
        <span className="page-folio">{totalPicked}/{slotLevels.length} escolhidos</span>
      </header>
      <h2 className="page-title" style={{ fontSize: 26 }}>Progressão</h2>
      <p className="page-subtitle">Poderes ganhos por nível. Slots marcados com ◆ exigem escolha.</p>

      {!powersData && (
        <div className="cc-skeleton">
          {[...Array(3)].map((_, i) => <div key={i} className="cc-skeleton-row"/>)}
        </div>
      )}

      {powersData && (
        <div className="cc-pick-list">
          {budget.map((slot, slotIdx) => (
            <PowerLevelRow
              key={slot.level}
              slot={slot}
              slotIndex={slotIdx}
              powersData={powersData}
              currentPick={powerPicks[slotIdx] || null}
              allPicks={powerPicks}
              onPick={(power) => setPowerPick(slotIdx, power)}
              onShowPower={onShowPower}
              classData={classData}
            />
          ))}
        </div>
      )}
    </div>
  );
}

/* ============================================================
   POWER LEVEL ROW
   ============================================================ */

function PowerLevelRow({ slot, slotIndex, powersData, currentPick, allPicks, onPick, onShowPower, classData }) {
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState("");
  const rootRef = useRef();
  const attrTotals = useAttrTotals();

  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (!rootRef.current?.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);

  const classPowers = powersData?.powers?.class || [];
  /* Poderes Concedidos só aparecem para classes devotas com divindade salva */
  const generalPowers = filterGrantedPowers(powersData?.powers?.general || [], classData);

  const otherPickIds = new Set(
    allPicks.filter((p, i) => p && i !== slotIndex).map((p) => p.id)
  );

  const filterPowers = (list) => {
    const q = query.toLowerCase().trim();
    return list.filter((p) => !otherPickIds.has(p.id) && (!q || p.name.toLowerCase().includes(q)));
  };

  const filteredClass = filterPowers(classPowers);
  const filteredGeneral = filterPowers(generalPowers);

  // Levels without a power slot: show fixed features only
  if (slot.power_slots === 0) {
    if (slot.fixed_features.length === 0) return null;
    return (
      <div className="cc-pick-row cc-pick-row--fixed">
        <div className="cc-pick-level">
          <span className="cc-level-num">{slot.level}</span>
          <span className="cc-level-cap">Nível</span>
        </div>
        <div className="cc-pick-circles">
          {slot.fixed_features.map((f) => (
            <span key={f} className="cc-circle-chip cc-feature-chip">{f}</span>
          ))}
        </div>
      </div>
    );
  }

  return (
    <div className="cc-pick-row" ref={rootRef}>
      <div className="cc-pick-level">
        <span className="cc-level-num">{slot.level}</span>
        <span className="cc-level-cap">Nível</span>
      </div>
      <div style={{ flex: 1 }}>
        {slot.fixed_features.length > 0 && (
          <div className="cc-pick-circles" style={{ marginBottom: 4 }}>
            {slot.fixed_features.map((f) => (
              <span key={f} className="cc-circle-chip cc-feature-chip">{f}</span>
            ))}
          </div>
        )}
        <div className="cc-pick-spell">
          {currentPick ? (() => {
            const pickPrereq = meetsAttrPrereqs(currentPick, attrTotals);
            const chosenCls = "cc-spell-chosen" + (pickPrereq.ok ? "" : " is-conflict");
            return (
            <button className={chosenCls} onClick={() => setOpen((o) => !o)}>
              <div>
                <span className="cc-spell-name">{currentPick.name}</span>
                <span className="cc-spell-meta">
                  <Kw kind="power-type">{currentPick.power_type}</Kw>
                  {!pickPrereq.ok && (
                    <span className="cc-opt-prereq">
                      Requer {fmtPrereqLabel(pickPrereq.missing)}
                    </span>
                  )}
                </span>
              </div>
              <span className="cc-spell-actions">
                <span className="cc-spell-info" onClick={(e) => { e.stopPropagation(); onShowPower(currentPick); }} title="Ver detalhes">ⓘ</span>
                <span className="cc-spell-clear" onClick={(e) => { e.stopPropagation(); onPick(null); }} title="Limpar">✕</span>
              </span>
            </button>
          );
          })() : (
            <button className="cc-spell-empty" onClick={() => setOpen((o) => !o)}>
              <span>◆ Escolher poder…</span>
              <span className="cc-chevron">▾</span>
            </button>
          )}

          {open && (
            <div className="cc-spell-dropdown">
              <input
                autoFocus
                className="cc-spell-search"
                placeholder="Buscar poder…"
                value={query}
                onChange={(e) => setQuery(e.target.value)}
              />
              <div className="cc-spell-options">
                {filteredClass.length > 0 && (
                  <>
                    <div className="cc-spell-divider">{slot.power_category}</div>
                    {filteredClass.map((p) => {
                      const prereq = meetsAttrPrereqs(p, attrTotals);
                      const cls = "cc-spell-opt" + (prereq.ok ? "" : " is-locked");
                      return (
                        <div key={p.id} className={cls}
                          onClick={() => {
                            if (!prereq.ok) return;
                            onPick(p); setOpen(false); setQuery("");
                          }}
                          title={prereq.ok ? "" : `Requer ${fmtPrereqLabel(prereq.missing)}`}>
                          <span className="cc-opt-name">{p.name}</span>
                          <span className="cc-opt-meta">
                            <Kw kind="power-type">{p.power_type}</Kw>
                            {!prereq.ok && (
                              <span className="cc-opt-prereq">
                                Requer {fmtPrereqLabel(prereq.missing)}
                              </span>
                            )}
                          </span>
                        </div>
                      );
                    })}
                  </>
                )}
                {filteredGeneral.length > 0 && (
                  <>
                    <div className="cc-spell-divider">Poderes gerais</div>
                    {filteredGeneral.map((p) => {
                      const prereq = meetsAttrPrereqs(p, attrTotals);
                      const cls = "cc-spell-opt" + (prereq.ok ? "" : " is-locked");
                      return (
                        <div key={p.id} className={cls}
                          onClick={() => {
                            if (!prereq.ok) return;
                            onPick(p); setOpen(false); setQuery("");
                          }}
                          title={prereq.ok ? "" : `Requer ${fmtPrereqLabel(prereq.missing)}`}>
                          <span className="cc-opt-name">{p.name}</span>
                          <span className="cc-opt-meta">
                            <Kw kind="power-type">{p.power_type}</Kw>
                            {!prereq.ok && (
                              <span className="cc-opt-prereq">
                                Requer {fmtPrereqLabel(prereq.missing)}
                              </span>
                            )}
                          </span>
                        </div>
                      );
                    })}
                  </>
                )}
                {filteredClass.length === 0 && filteredGeneral.length === 0 && (
                  <div className="cc-spell-empty-state">Nenhum poder encontrado.</div>
                )}
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   POWER DETAIL MODAL
   ============================================================ */

function PowerDetail({ power, onClose }) {
  if (!power) return null;
  const paras = (power.description || "").split("\n\n").filter(Boolean);
  return (
    <div className="cc-detail-backdrop" onClick={onClose}>
      <div className="cc-detail" onClick={(e) => e.stopPropagation()}>
        <button className="cc-detail-close" onClick={onClose} aria-label="Fechar">×</button>
        <header className="page-header" style={{ marginBottom: 14 }}>
          <span className="page-eyebrow">Poder</span>
          <span className="page-folio">{power.power_type}</span>
        </header>
        <h1 className="page-title">{power.name}</h1>
        <p className="page-subtitle">
          <Kw kind="power-type">{power.power_type}</Kw>
          {power.power_category ? ` · ${power.power_category}` : ""}
        </p>
        {power.prerequisites && (
          <dl className="statblock" style={{ gridTemplateColumns: "1fr" }}>
            <div><dt>Pré-requisito</dt><dd><Parsed>{power.prerequisites}</Parsed></dd></div>
          </dl>
        )}
        <div className="divider">❦</div>
        <div className="page-body" style={{ fontSize: 14 }}>
          {paras.map((p, i) => <p key={i}><Parsed>{p}</Parsed></p>)}
        </div>
      </div>
    </div>
  );
}

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

/* ============================================================
   LS helpers para multiclasse
   Formato novo:
     { classes: [{className, level, picks:ids[], powerPicks:ids[]}],
       activeIndex, activeTab }
   ============================================================ */
function loadCharClass() {
  try {
    const raw = JSON.parse(localStorage.getItem("arton_char_class"));
    if (raw && Array.isArray(raw.classes)) return raw;
  } catch {}
  return { classes: [], activeIndex: 0, activeTab: "magias" };
}

function saveCharClass(data) {
  try { localStorage.setItem("arton_char_class", JSON.stringify(data)); }
  catch {}
  try { window.dispatchEvent(new CustomEvent("arton:char-changed")); } catch {}
}

function ClassCreator() {
  /* top-level multiclasse state */
  const [data, setData] = useState(loadCharClass);

  /* derivar classe ativa */
  const allClasses = window.CLASSES || [];
  const activeEntry = data.classes[data.activeIndex] || null;
  const selectedClass = activeEntry?.className || null;
  const characterLevel = activeEntry?.level || 1;
  const activeTab = data.activeTab;
  const setActiveTab = (tab) => setData((d) => ({ ...d, activeTab: tab }));

  /* picks/powers locais (espelham IDs no entry ativo) */
  const [spellsData, setSpellsData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [picks, setPicks] = useState([]);
  const [summaryOpen, setSummaryOpen] = useState(false);
  const [detailSpell, setDetailSpell] = useState(null);
  const [powersData, setPowersData] = useState(null);
  const [powerPicks, setPowerPicks] = useState([]);
  const [detailPower, setDetailPower] = useState(null);
  const restoredRef = useRef(false);
  const powerRestoredRef = useRef(false);
  /* Rastreia para qual classe os picks/powerPicks LOCAIS pertencem.
     Quando o usuário troca de classe ativa, esses ficam null durante a
     transição. O save aborta enquanto não combinarem com selectedClass. */
  const picksOwnerRef = useRef(null);
  const powerPicksOwnerRef = useRef(null);
  const [spellPicksReady, setSpellPicksReady] = useState(false);
  const [powerPicksReady, setPowerPicksReady] = useState(false);

  const classData = useMemo(
    () => (selectedClass ? allClasses.find((c) => c.name === selectedClass) : null),
    [selectedClass, allClasses]
  );

  const debouncedLevel = useDebounced(characterLevel, 300);

  /* persistir toda alteração no LS */
  useEffect(() => { saveCharClass(data); }, [data]);

  /* === mutators do estado multiclasse === */

  const addClass = () => setData((d) => {
    const next = {
      ...d,
      classes: [...d.classes, { className: null, level: 1, path: null, lineage: null, picks: [], powerPicks: [] }],
      activeIndex: d.classes.length,
    };
    return next;
  });

  const removeClass = (idx) => setData((d) => {
    const nextClasses = d.classes.filter((_, i) => i !== idx);
    let newActive = d.activeIndex;
    if (idx < d.activeIndex) newActive--;
    if (newActive >= nextClasses.length) newActive = Math.max(0, nextClasses.length - 1);
    return { ...d, classes: nextClasses, activeIndex: newActive };
  });

  const changeClassName = (idx, name) => setData((d) => {
    const nextClasses = d.classes.slice();
    const cls = (window.CLASSES || []).find((c) => c.name === name);
    nextClasses[idx] = {
      ...nextClasses[idx],
      className: name,
      path: null,
      lineage: null,
      picks: [],
      powerPicks: [],
    };
    return {
      ...d,
      classes: nextClasses,
      activeTab: cls?.is_caster ? "magias" : (cls ? "poderes" : d.activeTab),
    };
  });

  const changeClassLevel = (idx, lvl) => setData((d) => {
    const nextClasses = d.classes.slice();
    nextClasses[idx] = { ...nextClasses[idx], level: lvl };
    return { ...d, classes: nextClasses };
  });

  /* sub-arquétipo (path) — reseta lineage e picks de poderes que dependem dele */
  const changeClassPath = (idx, path) => setData((d) => {
    const nextClasses = d.classes.slice();
    const cur = nextClasses[idx] || {};
    nextClasses[idx] = {
      ...cur,
      path: path || null,
      lineage: null,           /* troca de path invalida lineage */
      powerPicks: [],          /* poderes podem ficar inválidos */
    };
    return { ...d, classes: nextClasses };
  });

  /* sub-escolha encadeada (lineage) */
  const changeClassLineage = (idx, lineage) => setData((d) => {
    const nextClasses = d.classes.slice();
    const cur = nextClasses[idx] || {};
    nextClasses[idx] = {
      ...cur,
      lineage: lineage || null,
      powerPicks: [],          /* idem */
    };
    return { ...d, classes: nextClasses };
  });

  /* Toggle de escolha multi-select (ex: Escolas de Magia do Bardo).
     Liga/desliga o id na lista, respeitando max. Limpa picks de magia,
     pois magias antigas podem não pertencer mais às escolas escolhidas. */
  const toggleClassMulti = (idx, saveKey, id, max) => setData((d) => {
    const nextClasses = d.classes.slice();
    const cur = nextClasses[idx] || {};
    const list = Array.isArray(cur[saveKey]) ? cur[saveKey] : [];
    let nextList;
    if (list.includes(id)) {
      nextList = list.filter((x) => x !== id);
    } else if (!max || list.length < max) {
      nextList = [...list, id];
    } else {
      return d; /* limite atingido */
    }
    nextClasses[idx] = {
      ...cur,
      [saveKey]: nextList,
      picks: [],               /* magias antigas podem ficar inválidas */
    };
    return { ...d, classes: nextClasses };
  });

  const setActiveIndex = (i) => setData((d) => ({ ...d, activeIndex: i }));

  /* ao mudar a classe ATIVA ou seu nome, reseta locais e força restauração */
  useEffect(() => {
    const cls = (window.CLASSES || []).find((c) => c.name === selectedClass);
    setPicks([]);
    setPowerPicks([]);
    restoredRef.current = false;
    powerRestoredRef.current = false;
    picksOwnerRef.current = null;
    powerPicksOwnerRef.current = null;
    setSpellPicksReady(false);
    setPowerPicksReady(false);
    /* só forçar tab quando há classe — preserva escolha do usuário entre cards */
    if (selectedClass) {
      setData((d) => ({ ...d, activeTab: cls?.is_caster ? d.activeTab : "poderes" }));
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [data.activeIndex, selectedClass]);

  /* ao trocar path/linhagem, limpa o estado LOCAL de powerPicks.
     (o LS já é zerado pelo changeClassPath/changeClassLineage;
     este efeito sincroniza a UI sem refetch — powersData continua válido).
     NÃO invalidamos `powerPicksReady` aqui (não há refetch para reativar).
     A guarda do save effect tolera o estado vazio idempotentemente. */
  useEffect(() => {
    setPowerPicks([]);
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [activeEntry?.path, activeEntry?.lineage]);

  /* ao trocar escolhas multi-select que restringem magias (ex: escolas do Bardo),
     limpa o estado LOCAL de picks. O LS já foi zerado por toggleClassMulti. */
  const restrictsSpellSchool =
    classData?.path_metadata?.restricts === "spell_school";
  const multiKey = restrictsSpellSchool
    ? classData.path_metadata.save_key
    : null;
  const multiValue = multiKey ? activeEntry?.[multiKey] : null;
  useEffect(() => {
    if (multiKey) setPicks([]);
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [multiKey, multiValue]);

  // fetch dos spells sempre que classe ou (debounced) nível mudam.
  // IMPORTANTE: abortamos se debouncedLevel ainda não acompanhou characterLevel.
  // Sem isso, na troca de classe ativa, o fetch usa o nível STALE da classe
  // anterior, retornando slotCount errado e truncando picks da classe atual.
  useEffect(() => {
    let cancelled = false;
    if (!selectedClass) { setSpellsData(null); return; }
    if (debouncedLevel !== characterLevel) return;  // debounce settling — aguardar
    const fetchOwner = selectedClass;
    setLoading(true);
    window.fetchClassSpells(selectedClass, debouncedLevel)
      .then((d) => {
        if (cancelled) return;
        setSpellsData(d);
        /* Achata o budget em uma lista de slots: cada slot herda
           available_circles do seu nível. Pode haver N slots por nível
           (ex.: Bardo tem 2 no L1). */
        const flatSlots = [];
        for (const b of d.budget_by_level) {
          for (let i = 0; i < (b.picks || 0); i++) {
            flatSlots.push(b.available_circles);
          }
        }
        const totalSlots = flatSlots.length;

        // Primeira carga: tenta restaurar picks salvos no entry ativo
        if (!restoredRef.current) {
          restoredRef.current = true;
          const savedIds = activeEntry?.picks || [];
          if (savedIds.length) {
            const allSpells = Object.values(d.spells).flat();
            const next = new Array(totalSlots).fill(null);
            for (let i = 0; i < Math.min(savedIds.length, totalSlots); i++) {
              if (!savedIds[i]) continue;
              const spell = allSpells.find(s => s.id === savedIds[i]);
              if (!spell) continue;
              const allowed = flatSlots[i];
              if (allowed && allowed.includes(spell.circle)) next[i] = spell;
            }
            setPicks(next);
            picksOwnerRef.current = fetchOwner;
            setSpellPicksReady(true);
            return;
          }
          picksOwnerRef.current = fetchOwner;
          setSpellPicksReady(true);
        }
        /* Re-trim de picks ao mudar nível/classe:
           preserva apenas picks válidos para o slot daquela posição. */
        setPicks((prev) => {
          const next = new Array(totalSlots).fill(null);
          for (let i = 0; i < Math.min(prev.length, totalSlots); i++) {
            const p = prev[i];
            if (!p) continue;
            const allowed = flatSlots[i];
            if (allowed && allowed.includes(p.circle)) next[i] = p;
          }
          return next;
        });
      })
      .catch((err) => {
        if (!cancelled) {
          console.warn("fetchClassSpells failed:", err);
          setSpellsData(null);
        }
      })
      .finally(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, [selectedClass, debouncedLevel, characterLevel]);

  // fetch dos poderes sempre que classe ou (debounced) nível mudam.
  // IMPORTANTE: abortamos se debouncedLevel ainda não acompanhou characterLevel
  // (mesmo motivo do fetch de spells acima — evita fetch com nível STALE da
  // classe anterior, que truncaria os picks da classe atual).
  useEffect(() => {
    let cancelled = false;
    if (!selectedClass) { setPowersData(null); return; }
    if (debouncedLevel !== characterLevel) return;  // debounce settling — aguardar
    // captura a identidade da classe para a qual estamos buscando — usada
    // tanto na restauração quanto no marcador de owner abaixo
    const fetchOwner = selectedClass;
    window.fetchClassPowers(selectedClass, debouncedLevel)
      .then((d) => {
        if (cancelled) return;
        setPowersData(d);
        const slotCount = (d.budget_by_level || []).length;
        // primeira carga: restaurar picks salvos
        if (!powerRestoredRef.current) {
          powerRestoredRef.current = true;
          const savedIds = activeEntry?.powerPicks || [];
          if (savedIds.length) {
            /* aplica filtro de poderes concedidos antes de restaurar */
            const cls = (window.CLASSES || []).find((c) => c.name === selectedClass);
            const filteredGeneral = filterGrantedPowers(d.powers?.general || [], cls);
            const allPowers = [...(d.powers?.class || []), ...filteredGeneral];
            const next = new Array(slotCount).fill(null);
            for (let i = 0; i < Math.min(savedIds.length, slotCount); i++) {
              if (!savedIds[i]) continue;
              const power = allPowers.find((p) => p.id === savedIds[i]);
              if (power) next[i] = power;
            }
            setPowerPicks(next);
            powerPicksOwnerRef.current = fetchOwner;
            setPowerPicksReady(true);
            return;
          }
          powerPicksOwnerRef.current = fetchOwner;
          setPowerPicksReady(true);
        }
        setPowerPicks((prev) => {
          if (prev.length <= slotCount) return prev;
          return prev.slice(0, slotCount);
        });
      })
      .catch((err) => { if (!cancelled) { console.warn("fetchClassPowers failed:", err); setPowersData(null); } });
    return () => { cancelled = true; };
  }, [selectedClass, debouncedLevel, characterLevel]);

  /* salvar picks no entry ativo sempre que mudam.
     NÃO incluímos `selectedClass` nas deps porque, ao trocar de classe ativa,
     este effect dispararia ANTES do reset zerar o estado local — e gravaria
     os picks da classe antiga no slot da classe nova. O guard
     `*PicksReady` já fica `false` durante a transição (zerado no reset
     effect) e só volta a `true` após a restauração da nova classe. */
  useEffect(() => {
    if (!selectedClass || !spellPicksReady) return;
    // owner ref = a classe para a qual `picks` foi restaurado.
    // Se não bate com a classe atual, estamos em transição — aborta.
    if (picksOwnerRef.current !== selectedClass) return;
    const ids = picks.map((p) => p?.id || null);
    setData((d) => {
      const next = d.classes.slice();
      const entry = next[d.activeIndex];
      if (!entry || entry.className !== selectedClass) return d;
      next[d.activeIndex] = { ...entry, picks: ids };
      return { ...d, classes: next };
    });
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [picks, spellPicksReady]);

  useEffect(() => {
    if (!selectedClass || !powerPicksReady) return;
    if (powerPicksOwnerRef.current !== selectedClass) return;
    const ids = powerPicks.map((p) => p?.id || null);
    setData((d) => {
      const next = d.classes.slice();
      const entry = next[d.activeIndex];
      if (!entry || entry.className !== selectedClass) return d;
      next[d.activeIndex] = { ...entry, powerPicks: ids };
      return { ...d, classes: next };
    });
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [powerPicks, powerPicksReady]);

  const setPick = (slotIdx, spell) => {
    setPicks((prev) => {
      const next = prev.slice();
      while (next.length <= slotIdx) next.push(null);
      next[slotIdx] = spell;
      return next;
    });
  };

  const setPowerPick = (slotIndex, power) => {
    setPowerPicks((prev) => {
      const next = prev.slice();
      while (next.length <= slotIndex) next.push(null);
      next[slotIndex] = power;
      return next;
    });
  };

  /* empty state — nenhuma classe ainda */
  if (data.classes.length === 0) {
    return (
      <div className="cc-root">
        <div className="spread-frame">
          <div className="page left">
            <CreatorCorners/>
            <ClassListPanel
              classes={allClasses}
              allClasses={allClasses}
              classEntries={data.classes}
              activeIndex={data.activeIndex}
              setActiveIndex={setActiveIndex}
              onAdd={addClass}
              onRemove={removeClass}
              onChangeClass={changeClassName}
              onChangeLevel={changeClassLevel}
              onOpenSummary={() => setSummaryOpen(true)}
            />
          </div>
          <div className="page right">
            <CreatorCorners/>
            <div className="page-content">
              <header className="page-header">
                <span className="page-eyebrow">Picks</span>
                <span className="page-folio">—</span>
              </header>
              <div className="empty-state">
                <div className="ornament">✦ ✦ ✦</div>
                <div>Adicione uma classe à esquerda para começar.</div>
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="cc-root">
      <div className="spread-frame">
        <div className="page left">
          <CreatorCorners/>
          <ClassListPanel
            classes={allClasses}
            allClasses={allClasses}
            classEntries={data.classes}
            activeIndex={data.activeIndex}
            setActiveIndex={setActiveIndex}
            onAdd={addClass}
            onRemove={removeClass}
            onChangeClass={changeClassName}
            onChangeLevel={changeClassLevel}
            onChangePath={changeClassPath}
            onChangeLineage={changeClassLineage}
            onToggleMulti={toggleClassMulti}
            onOpenSummary={() => setSummaryOpen(true)}
          />
        </div>
        <div className="page right">
          <CreatorCorners/>
          <div className="cc-tabs">
            {classData?.is_caster && (
              <button
                className={"cc-tab" + (activeTab === "magias" ? " active" : "")}
                onClick={() => setActiveTab("magias")}
              >
                Magias
              </button>
            )}
            <button
              className={"cc-tab" + (activeTab === "poderes" ? " active" : "")}
              onClick={() => setActiveTab("poderes")}
            >
              Poderes
            </button>
          </div>
          {activeTab === "magias" ? (
            <>
              <PicksPanel
                classData={classData}
                spellsData={spellsData}
                loading={loading}
                picks={picks}
                setPick={setPick}
                onShowSpell={setDetailSpell}
                restrictedSchools={
                  classData?.path_metadata?.restricts === "spell_school"
                    ? activeEntry?.[classData.path_metadata.save_key] || []
                    : null
                }
              />
              <SummaryDrawer
                open={summaryOpen}
                onClose={() => setSummaryOpen(false)}
                picks={picks}
                classData={classData}
                spellsData={spellsData}
                onShowSpell={setDetailSpell}
              />
            </>
          ) : (
            <PowerPicksPanel
              classData={classData}
              powersData={powersData}
              powerPicks={powerPicks}
              setPowerPick={setPowerPick}
              onShowPower={setDetailPower}
            />
          )}
        </div>
      </div>
      <SpellDetail spell={detailSpell} onClose={() => setDetailSpell(null)}/>
      <PowerDetail power={detailPower} onClose={() => setDetailPower(null)}/>
    </div>
  );
}

window.ClassCreator = ClassCreator;
