// attr-panel.jsx — Painel lateral persistente de atributos do personagem
// Expõe: window.CHAR_ATTRS (store) + window.AttrPanel (componente React)
//
// Revisão visual + interativa: estética grimório (pergaminho/gilt/Cinzel),
// colapsável com handle vertical, breakdowns em hover, animação numérica,
// tooltips de fórmula, e melhor feedback de compra/rolagem.

const ATTRS = ["FOR", "DES", "CON", "INT", "SAB", "CAR"];
const ATTR_LABELS = {
  FOR: "Força", DES: "Destreza", CON: "Constituição",
  INT: "Inteligência", SAB: "Sabedoria", CAR: "Carisma",
};
const STORAGE_KEY = "arton_char_attrs";
const COLLAPSED_KEY = "arton_attr_collapsed";
const BUY_BUDGET = 10;
// custos T20 (valor → custo em pontos)
const BUY_COSTS = { "-2": -2, "-1": -1, "0": 0, "1": 1, "2": 2, "3": 4, "4": 7 };

function _defaultState() {
  return {
    method: "buy",
    base_buy: { FOR: 0, DES: 0, CON: 0, INT: 0, SAB: 0, CAR: 0 },
    base_roll: { FOR: 0, DES: 0, CON: 0, INT: 0, SAB: 0, CAR: 0 },
    rolls: null,
    rollAssignments: {},
  };
}

function _loadFromStorage() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (!raw) return _defaultState();
    return { ..._defaultState(), ...JSON.parse(raw) };
  } catch {
    return _defaultState();
  }
}

function _readRace() {
  try { const raw = localStorage.getItem("arton_char_race");   return raw ? JSON.parse(raw) : null; } catch { return null; }
}
function _readOrigin() {
  try { const raw = localStorage.getItem("arton_char_origin"); return raw ? JSON.parse(raw) : null; } catch { return null; }
}

function _attrAbbrevFromName(name) {
  if (!name) return null;
  const k = String(name).toLowerCase();
  if (k.startsWith("for")) return "FOR";
  if (k.startsWith("des")) return "DES";
  if (k.startsWith("con")) return "CON";
  if (k.startsWith("int")) return "INT";
  if (k.startsWith("sab")) return "SAB";
  if (k.startsWith("car")) return "CAR";
  return null;
}

function _parseBonusInt(bonus) {
  if (typeof bonus === "number") return bonus;
  if (!bonus) return 0;
  const m = String(bonus).match(/-?\d+/);
  return m ? parseInt(m[0], 10) : 0;
}

function _raceAttrMods() {
  const stored = _readRace();
  if (!stored?.id) return {};
  const full = (window.RACES || []).find((r) => r.id === stored.id);
  if (!full?.attr_modifiers) return {};
  const out = {};
  for (const [k, v] of Object.entries(full.attr_modifiers)) {
    if (k === "variable") continue;
    if (typeof v !== "number" || v === 0) continue;
    const abbrev = _attrAbbrevFromName(k);
    if (abbrev) out[abbrev] = (out[abbrev] || 0) + v;
  }
  /* Bônus da regra "Escolha X atributos +Y" (variable_picks salvos pelo race-creator). */
  const variableRule = full.attr_modifiers.variable;
  const picks = Array.isArray(stored.variable_picks) ? stored.variable_picks : null;
  if (variableRule && variableRule.amount && picks) {
    for (const attr of picks) {
      if (!attr) continue;
      const abbrev = _attrAbbrevFromName(attr);
      if (abbrev) out[abbrev] = (out[abbrev] || 0) + variableRule.amount;
    }
  }
  return out;
}

function _originAttrMods() {
  const stored = _readOrigin();
  if (!stored?.id) return {};
  const full = (window.ORIGINS || []).find((o) => o.id === stored.id);
  if (!full?.benefits) return {};
  const out = {};
  const picks = Array.isArray(stored.picks) ? stored.picks : [];
  for (let i = 0; i < full.benefits.length; i++) {
    const b = full.benefits[i];
    if (b?.type !== "attribute") continue;
    const isAuto = !b.choice;
    const isPicked = picks.includes(i);
    if (!isAuto && !isPicked) continue;
    const abbrev = _attrAbbrevFromName(b.name);
    if (!abbrev) continue;
    out[abbrev] = (out[abbrev] || 0) + _parseBonusInt(b.bonus);
  }
  return out;
}

function _computeTotals(state) {
  const raceMods = _raceAttrMods();
  const originMods = _originAttrMods();
  const base = state.method === "buy" ? state.base_buy : state.base_roll;
  const totals = {};
  for (const a of ATTRS) {
    totals[a] = (base[a] || 0) + (raceMods[a] || 0) + (originMods[a] || 0);
  }
  return totals;
}

function _computePointsSpent(buyBase) {
  return ATTRS.reduce((sum, a) => sum + (BUY_COSTS[String(buyBase[a])] ?? 0), 0);
}

(function createStore() {
  let state = _loadFromStorage();
  const listeners = new Set();

  function persist() {
    try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch {}
  }

  function getState() {
    return {
      method: state.method,
      base_buy: { ...state.base_buy },
      base_roll: { ...state.base_roll },
      rolls: state.rolls ? [...state.rolls] : null,
      rollAssignments: { ...state.rollAssignments },
      totals: _computeTotals(state),
      pointsSpent: _computePointsSpent(state.base_buy),
    };
  }

  function notify() {
    const snapshot = getState();
    listeners.forEach((fn) => { try { fn(snapshot); } catch (e) { console.error(e); } });
  }

  function setBase(attr, val) {
    if (!ATTRS.includes(attr)) return;
    if (val < -2 || val > 4) return;
    if (state.method === "buy") {
      /* Floor T20: total (base + raça + origem) não pode ficar abaixo de -2.
         Ao reduzir, cada passo já gera refund de orçamento via BUY_COSTS. */
      const raceMods = _raceAttrMods();
      const originMods = _originAttrMods();
      const total = val + (raceMods[attr] || 0) + (originMods[attr] || 0);
      if (total < -2) return;
      const next = { ...state.base_buy, [attr]: val };
      if (_computePointsSpent(next) > BUY_BUDGET) return;
      state.base_buy = next;
    } else {
      state.base_roll = { ...state.base_roll, [attr]: val };
    }
    persist(); notify();
  }

  function setMethod(m) {
    if (m !== "buy" && m !== "roll") return;
    state.method = m;
    persist(); notify();
  }

  function setRolls(arr) {
    state.rolls = arr;
    state.rollAssignments = {};
    state.base_roll = { FOR: 0, DES: 0, CON: 0, INT: 0, SAB: 0, CAR: 0 };
    persist(); notify();
  }

  function assignRoll(attr, rollIdx) {
    if (!ATTRS.includes(attr) || !state.rolls) return;
    const prev = { ...state.rollAssignments };
    for (const k of Object.keys(prev)) {
      if (prev[k] === rollIdx) delete prev[k];
    }
    if (rollIdx === null || rollIdx === undefined) delete prev[attr];
    else prev[attr] = rollIdx;
    state.rollAssignments = prev;
    const nextBase = { FOR: 0, DES: 0, CON: 0, INT: 0, SAB: 0, CAR: 0 };
    for (const a of ATTRS) {
      const idx = prev[a];
      if (idx !== undefined) nextBase[a] = state.rolls[idx];
    }
    state.base_roll = nextBase;
    persist(); notify();
  }

  function reset() {
    state = _defaultState();
    persist(); notify();
  }

  function subscribe(fn) {
    listeners.add(fn);
    return () => listeners.delete(fn);
  }

  window.CHAR_ATTRS = {
    getState, setBase, setMethod, setRolls, assignRoll, reset, subscribe,
    ATTRS, BUY_BUDGET, BUY_COSTS,
  };

  window.addEventListener("arton:char-changed", notify);

  if (window.DATA_READY && typeof window.DATA_READY.then === "function") {
    window.DATA_READY.then(notify, () => {});
  }
})();

// ---------------------------------------------------------------------------
// Hooks
// ---------------------------------------------------------------------------

function useCharAttrs() {
  const [state, setState] = React.useState(() => window.CHAR_ATTRS.getState());
  React.useEffect(() => window.CHAR_ATTRS.subscribe(setState), []);
  return state;
}

// AnimatedNumber — anima transição entre dois inteiros com um pulse.
function AnimatedNumber({ value, format = (v) => v }) {
  const prev = React.useRef(value);
  const [pulse, setPulse] = React.useState(null);
  React.useEffect(() => {
    if (prev.current !== value) {
      setPulse(value > prev.current ? "up" : "down");
      prev.current = value;
      const t = setTimeout(() => setPulse(null), 400);
      return () => clearTimeout(t);
    }
  }, [value]);
  return <span className={`ap-num ${pulse ? "ap-pulse-" + pulse : ""}`}>{format(value)}</span>;
}

// ---------------------------------------------------------------------------
// AttrCard — Card de um único atributo, com hover-breakdown
// ---------------------------------------------------------------------------

function AttrCard({ attr, total, base, raceMod, originMod, method }) {
  const fmt = (n) => (n >= 0 ? `+${n}` : `${n}`);
  const tone = total > 0 ? "pos" : total < 0 ? "neg" : "neutral";
  const ref = React.useRef(null);
  const [pos, setPos] = React.useState(null);

  // Recomputa a posição do popover quando ativo (caso o painel role).
  function computePos() {
    const el = ref.current;
    if (!el) return null;
    const r = el.getBoundingClientRect();
    const popH = 150;
    const popW = 230;     // 200 content + 2 border + 24 padding + safety
    const margin = 12;
    const above = r.top - margin - popH > 8;
    // centro horizontal preferido — depois cortamos pra caber no viewport
    let cx = r.left + r.width / 2;
    const halfW = popW / 2;
    if (cx + halfW > window.innerWidth - margin) cx = window.innerWidth - margin - halfW;
    if (cx - halfW < margin) cx = margin + halfW;
    return {
      left: cx,
      top: above ? r.top - margin : r.bottom + margin,
      // deslocamento da setinha (em px relativos ao centro do popover) — pra que aponte para o card mesmo após o clamp
      arrowOffset: r.left + r.width / 2 - cx,
      placement: above ? "above" : "below",
    };
  }
  function activate() { setPos(computePos()); }
  function deactivate() { setPos(null); }

  React.useEffect(() => {
    if (!pos) return;
    function onScrollOrResize() { setPos(computePos()); }
    window.addEventListener("scroll", onScrollOrResize, true);
    window.addEventListener("resize", onScrollOrResize);
    return () => {
      window.removeEventListener("scroll", onScrollOrResize, true);
      window.removeEventListener("resize", onScrollOrResize);
    };
  }, [pos != null]);

  const popover = pos && ReactDOM.createPortal(
    <div
      className={`ap-attr-popover ap-attr-popover--${pos.placement}`}
      style={{ left: pos.left + "px", top: pos.top + "px", "--ap-pop-arrow": pos.arrowOffset + "px" }}
      role="tooltip"
    >
      <div className="ap-pop-title">{ATTR_LABELS[attr]}</div>
      <ul className="ap-pop-list">
        <li><span className="ap-pop-dot" data-src="base"/>base ({method === "buy" ? "compra" : "rolagem"})<em>{fmt(base || 0)}</em></li>
        {raceMod ? <li><span className="ap-pop-dot" data-src="race"/>raça<em>{fmt(raceMod)}</em></li> : null}
        {originMod ? <li><span className="ap-pop-dot" data-src="origin"/>origem<em>{fmt(originMod)}</em></li> : null}
        <li className="ap-pop-total">total<em>{fmt(total)}</em></li>
      </ul>
    </div>,
    document.body
  );

  return (
    <div
      ref={ref}
      className={`ap-attr ap-attr--${tone}`}
      tabIndex={0}
      onMouseEnter={activate}
      onMouseLeave={deactivate}
      onFocus={activate}
      onBlur={deactivate}
    >
      <div className="ap-attr-frame">
        <div className="ap-attr-label" title={ATTR_LABELS[attr]}>{attr}</div>
        <div className="ap-attr-total">
          <AnimatedNumber value={total} format={fmt} />
        </div>
        <div className="ap-attr-chips">
          {base !== 0 && (
            <span className="ap-chip ap-chip--base" title={method === "buy" ? "Base (compra)" : "Base (rolagem)"}>
              {fmt(base)}
            </span>
          )}
          {raceMod !== 0 && (
            <span className="ap-chip ap-chip--race" title="Raça">{fmt(raceMod)}</span>
          )}
          {originMod !== 0 && (
            <span className="ap-chip ap-chip--origin" title="Origem">{fmt(originMod)}</span>
          )}
          {base === 0 && raceMod === 0 && originMod === 0 && (
            <span className="ap-chip ap-chip--empty">—</span>
          )}
        </div>
      </div>
      {popover}
    </div>
  );
}

// ---------------------------------------------------------------------------
// Buy mode — com barra de orçamento
// ---------------------------------------------------------------------------

function BuyConfig({ state }) {
  const { base_buy, pointsSpent, totals } = state;
  const remaining = window.CHAR_ATTRS.BUY_BUDGET - pointsSpent;

  function canIncrement(attr) {
    const cur = base_buy[attr];
    if (cur >= 4) return false;
    const nextCost = window.CHAR_ATTRS.BUY_COSTS[String(cur + 1)];
    const curCost = window.CHAR_ATTRS.BUY_COSTS[String(cur)];
    return remaining + curCost - nextCost >= 0;
  }

  /* Não permite reduzir abaixo do floor -2 no total (incl. mods de raça/origem). */
  function canDecrement(attr) {
    const cur = base_buy[attr];
    if (cur <= -2) return false;
    /* O total atual já é (cur + raceMod + originMod). Próximo total = total-1. */
    return (totals?.[attr] ?? cur) - 1 >= -2;
  }

  const fmt = (n) => (n >= 0 ? `+${n}` : `${n}`);
  const pct = (pointsSpent / window.CHAR_ATTRS.BUY_BUDGET) * 100;

  return (
    <div className="ap-buy">
      <div className="ap-buy-budget">
        <div className="ap-budget-meter" role="meter" aria-valuemin={0} aria-valuemax={window.CHAR_ATTRS.BUY_BUDGET} aria-valuenow={pointsSpent}>
          <div className="ap-budget-fill" style={{ width: `${pct}%` }} />
          {Array.from({ length: window.CHAR_ATTRS.BUY_BUDGET - 1 }).map((_, i) => (
            <span key={i} className="ap-budget-tick" style={{ left: `${((i+1) / window.CHAR_ATTRS.BUY_BUDGET) * 100}%` }} />
          ))}
        </div>
        <div className="ap-budget-label">
          <span>orçamento</span>
          <strong>{pointsSpent} / {window.CHAR_ATTRS.BUY_BUDGET}</strong>
        </div>
      </div>

      <table className="ap-buy-table">
        <tbody>
          {window.CHAR_ATTRS.ATTRS.map((a) => {
            const cur = base_buy[a];
            const nextCost = window.CHAR_ATTRS.BUY_COSTS[String(cur + 1)];
            return (
              <tr key={a}>
                <td className="ap-buy-attr">{a}</td>
                <td>
                  <button
                    className="ap-buy-step"
                    aria-label={`Diminuir ${ATTR_LABELS[a]}`}
                    disabled={!canDecrement(a)}
                    title={!canDecrement(a) ? "Total já está no mínimo (-2)" : ""}
                    onClick={() => window.CHAR_ATTRS.setBase(a, cur - 1)}
                  >−</button>
                </td>
                <td className="ap-buy-val">{fmt(cur)}</td>
                <td>
                  <button
                    className="ap-buy-step"
                    aria-label={`Aumentar ${ATTR_LABELS[a]}`}
                    disabled={!canIncrement(a)}
                    onClick={() => window.CHAR_ATTRS.setBase(a, cur + 1)}
                  >+</button>
                </td>
                <td className="ap-buy-cost" title={cur < 4 ? `Próximo custa ${nextCost} pt` : "Máximo"}>
                  {cur < 4 ? `→${nextCost}pt` : "máx"}
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
      <button className="ap-buy-reset" onClick={() => window.CHAR_ATTRS.reset()}>
        <span>↺</span> Resetar
      </button>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Derivados (PV, PM, Defesa, CD, Perícias)
// ---------------------------------------------------------------------------

function _readClasses() {
  try {
    const raw = localStorage.getItem("arton_char_class");
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    return parsed?.classes || null;
  } catch { return null; }
}

function _findClassDef(name) {
  return (window.CLASSES || []).find((c) => c.name === name) || null;
}

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

function _attrAbbrev(name) {
  if (!name) return null;
  const k = String(name).toLowerCase();
  if (k.startsWith("for")) return "FOR";
  if (k.startsWith("des")) return "DES";
  if (k.startsWith("con")) return "CON";
  if (k.startsWith("int")) return "INT";
  if (k.startsWith("sab")) return "SAB";
  if (k.startsWith("car")) return "CAR";
  return null;
}

function computeDerivatives(state, classes) {
  const totals = state.totals;
  if (!classes || classes.length === 0) {
    return { pv: 0, pm: 0, defesa: 10 + (totals.DES || 0), cds: [], skills: null, totalLevel: 0, pvFormula: null, pmFormula: null };
  }
  const totalLevel = classes.reduce((s, c) => s + (c.level || 1), 0);
  const initial = classes[0];
  const initialDef = _findClassDef(initial.className);
  /* PV T20: classe inicial dá pv_initial + (nível-1) × pv_per_level.
     Classes adicionais (multiclasse) dão pv_per_level × níveis (sem o bloco inicial).
     CON aplica em todos os níveis. hp_die é só pra regeneração em descansos. */
  const initialBase = initialDef?.pv_initial || 0;
  const initialPerLvl = initialDef?.pv_per_level || 0;
  let pv = initialBase + ((initial.level || 1) - 1) * initialPerLvl;
  for (let i = 1; i < classes.length; i++) {
    const def = _findClassDef(classes[i].className);
    const perLvl = def?.pv_per_level || 0;
    pv += (classes[i].level || 1) * perLvl;
  }
  pv += (totals.CON || 0) * totalLevel;

  let pm = 0;
  for (const c of classes) {
    const def = _findClassDef(c.className);
    pm += (def?.pm_per_level || 0) * (c.level || 1);
  }

  const defesa = 10 + (totals.DES || 0);

  const cds = [];
  for (const c of classes) {
    const def = _findClassDef(c.className);
    if (!def?.is_caster) continue;
    const attrName = _castingAttrFor(def, c);
    const abbrev = _attrAbbrev(attrName);
    if (!abbrev) continue;
    const mod = totals[abbrev] || 0;
    cds.push({ className: c.className, attr: abbrev, value: 10 + Math.floor((c.level || 1) / 2) + mod });
  }

  const initialSkillPoints = initialDef?.skill_points || 0;
  const skills = initialSkillPoints + Math.max(0, totals.INT || 0);
  const skillsBase = initialSkillPoints;
  const skillsIntBonus = Math.max(0, totals.INT || 0);

  const pvFormula = `${initialBase} + ${(initial.level||1)-1}×${initialPerLvl} + CON×${totalLevel}`;
  const pmFormula = classes.map((c) => {
    const def = _findClassDef(c.className);
    return `${def?.pm_per_level || 0}×${c.level || 1}`;
  }).join(" + ");

  return { pv, pm, defesa, cds, skills, skillsBase, skillsIntBonus, totalLevel, pvFormula, pmFormula };
}

function rollFourDropLowest() {
  const dice = [0, 0, 0, 0].map(() => 1 + Math.floor(Math.random() * 6));
  dice.sort((a, b) => a - b);
  return dice[1] + dice[2] + dice[3];
}

function sumToMod(sum) {
  if (sum <= 7) return -2;
  if (sum <= 9) return -1;
  if (sum <= 11) return 0;
  if (sum <= 13) return 1;
  if (sum <= 15) return 2;
  if (sum <= 17) return 3;
  return 4;
}

// ---------------------------------------------------------------------------
// Roll mode
// ---------------------------------------------------------------------------

function RollConfig({ state }) {
  const { rolls, rollAssignments } = state;
  const [selectedChip, setSelectedChip] = React.useState(null);

  function doRoll() {
    if (rolls && Object.keys(rollAssignments).length > 0) {
      if (!confirm("Descartar atribuições atuais e rolar de novo?")) return;
    }
    const mods = [0, 0, 0, 0, 0, 0].map(() => sumToMod(rollFourDropLowest()));
    window.CHAR_ATTRS.setRolls(mods);
    setSelectedChip(null);
  }

  function clickChip(idx) {
    setSelectedChip(selectedChip === idx ? null : idx);
  }

  function clickAttr(attr) {
    if (selectedChip === null) {
      if (rollAssignments[attr] !== undefined) {
        window.CHAR_ATTRS.assignRoll(attr, null);
      }
      return;
    }
    window.CHAR_ATTRS.assignRoll(attr, selectedChip);
    setSelectedChip(null);
  }

  const usedIdxs = new Set(Object.values(rollAssignments));
  const fmt = (n) => (n >= 0 ? `+${n}` : `${n}`);

  if (!rolls) {
    return (
      <div className="ap-roll">
        <p className="ap-roll-hint">Rola 4d6 (descarta o menor) seis vezes e converte em modificadores.</p>
        <button className="ap-roll-btn" onClick={doRoll}>
          <span aria-hidden>🎲</span> Rolar 4d6 × 6
        </button>
      </div>
    );
  }

  return (
    <div className="ap-roll">
      <p className="ap-roll-hint">
        {selectedChip === null ? "Toque uma rolagem, depois um atributo." : "Agora toque o atributo destino."}
      </p>
      <div className="ap-roll-chips">
        {rolls.map((mod, i) => (
          <button
            key={i}
            className={`ap-chip-roll ${usedIdxs.has(i) ? "is-used" : ""} ${selectedChip === i ? "is-selected" : ""}`}
            disabled={usedIdxs.has(i)}
            onClick={() => clickChip(i)}
            aria-label={`Rolagem ${i+1}: ${fmt(mod)}`}
          >{fmt(mod)}</button>
        ))}
      </div>
      <div className="ap-roll-slots">
        {window.CHAR_ATTRS.ATTRS.map((a) => {
          const idx = rollAssignments[a];
          const mod = idx !== undefined ? rolls[idx] : null;
          const filled = mod !== null;
          return (
            <button
              key={a}
              className={`ap-roll-slot ${filled ? "is-filled" : ""} ${selectedChip !== null && !filled ? "is-target" : ""}`}
              onClick={() => clickAttr(a)}
              aria-label={`${ATTR_LABELS[a]}: ${filled ? fmt(mod) : "vazio"}`}
            >
              <span className="ap-slot-attr">{a}</span>
              <span className="ap-slot-val">{filled ? fmt(mod) : "—"}</span>
            </button>
          );
        })}
      </div>
      <button className="ap-roll-reset" onClick={doRoll}>↺ Rolar de novo</button>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Conflitos com pré-requisitos
// ---------------------------------------------------------------------------

function computeConflicts(totals, classes) {
  if (!classes || !window.POWERS) return [];
  const pickedIds = new Set();
  for (const c of classes) {
    if (Array.isArray(c.powerPicks)) {
      for (const id of c.powerPicks) if (id) pickedIds.add(id);
    }
  }
  if (pickedIds.size === 0) return [];
  const conflicts = [];
  for (const p of window.POWERS) {
    if (!pickedIds.has(p.id)) continue;
    const parsed = p.prerequisites_parsed?.attrs;
    if (!parsed) continue;
    const missing = [];
    for (const [attr, min] of Object.entries(parsed)) {
      if ((totals[attr] ?? -99) < min) missing.push({ attr, min });
    }
    if (missing.length > 0) conflicts.push({ name: p.name, missing });
  }
  return conflicts;
}

function ConflictsSection({ conflicts }) {
  const [collapsed, setCollapsed] = React.useState(false);
  if (conflicts.length === 0) return null;
  return (
    <section className="ap-section ap-conflicts">
      <button className="ap-section-head" onClick={() => setCollapsed(!collapsed)} aria-expanded={!collapsed}>
        <span className="ap-section-marker">⚠</span>
        <span className="ap-section-title">Conflitos</span>
        <span className="ap-section-count">{conflicts.length}</span>
        <span className="ap-section-chevron">{collapsed ? "▸" : "▾"}</span>
      </button>
      {!collapsed && (
        <ul className="ap-conflict-list">
          {conflicts.map((c) => (
            <li key={c.name} className="ap-conflict">
              <strong>{c.name}</strong>
              <span>
                falta {c.missing.map((m) => `${m.attr} ${m.min >= 0 ? "+" : ""}${m.min}`).join(", ")}
              </span>
            </li>
          ))}
        </ul>
      )}
    </section>
  );
}

// ---------------------------------------------------------------------------
// DerivativesSection
// ---------------------------------------------------------------------------

function DerivativesSection({ deriv, totals }) {
  if (deriv.totalLevel === 0) {
    return (
      <section className="ap-section ap-deriv">
        <div className="ap-section-head ap-section-head--static">
          <span className="ap-section-marker">❦</span>
          <span className="ap-section-title">Derivados</span>
        </div>
        <p className="ap-deriv-hint">
          Escolha uma <em>classe</em> para ver PV, PM, CD e perícias.
        </p>
        <div className="ap-deriv-grid">
          <div className="ap-deriv-cell" title={`10 + DES (${totals.DES >= 0 ? '+' : ''}${totals.DES})`}>
            <span>Def</span>
            <strong><AnimatedNumber value={deriv.defesa}/></strong>
          </div>
        </div>
      </section>
    );
  }
  const single = deriv.cds.length === 1 ? deriv.cds[0] : null;
  return (
    <section className="ap-section ap-deriv">
      <div className="ap-section-head ap-section-head--static">
        <span className="ap-section-marker">❦</span>
        <span className="ap-section-title">Derivados</span>
        <span className="ap-section-count">nv {deriv.totalLevel}</span>
      </div>
      <div className="ap-deriv-grid">
        <div className="ap-deriv-cell" title={deriv.pvFormula || ""}>
          <span>PV</span>
          <strong><AnimatedNumber value={deriv.pv}/></strong>
        </div>
        <div className="ap-deriv-cell" title={deriv.pmFormula || ""}>
          <span>PM</span>
          <strong><AnimatedNumber value={deriv.pm}/></strong>
        </div>
        <div className="ap-deriv-cell" title={`10 + DES (${totals.DES >= 0 ? '+' : ''}${totals.DES})`}>
          <span>Def</span>
          <strong><AnimatedNumber value={deriv.defesa}/></strong>
        </div>
        {single && (
          <div className="ap-deriv-cell" title={`${single.className} · 10 + ⌊nv/2⌋ + ${single.attr}`}>
            <span>CD</span>
            <strong><AnimatedNumber value={single.value}/></strong>
          </div>
        )}
      </div>
      {deriv.cds.length > 1 && (
        <ul className="ap-deriv-cds">
          {deriv.cds.map((cd) => (
            <li key={cd.className}>
              <span>{cd.className}</span>
              <strong>CD {cd.value}</strong>
            </li>
          ))}
        </ul>
      )}
      {deriv.skills !== null && (
        <div className="ap-deriv-skills" title={`Base ${deriv.skillsBase} + INT ${deriv.skillsIntBonus > 0 ? '+' + deriv.skillsIntBonus : '+0'}`}>
          <span>Perícias treinadas</span>
          <strong><AnimatedNumber value={deriv.skills}/></strong>
        </div>
      )}
    </section>
  );
}

// ---------------------------------------------------------------------------
// Painel principal
// ---------------------------------------------------------------------------

function AttrPanel() {
  const state = useCharAttrs();
  const [configOpen, setConfigOpen] = React.useState(true);
  const [collapsed, setCollapsed] = React.useState(() => {
    try { return localStorage.getItem(COLLAPSED_KEY) === "1"; } catch { return false; }
  });

  // Persistir colapso + propagar p/ CSS root pra app-shell saber reservar espaço
  React.useEffect(() => {
    try { localStorage.setItem(COLLAPSED_KEY, collapsed ? "1" : "0"); } catch {}
    document.documentElement.setAttribute("data-attr-panel", collapsed ? "collapsed" : "expanded");
  }, [collapsed]);
  // garantir set inicial mesmo antes do useEffect rodar
  React.useEffect(() => {
    document.documentElement.setAttribute("data-attr-panel", collapsed ? "collapsed" : "expanded");
  }, []);

  const raceMods = _raceAttrMods();
  const originMods = _originAttrMods();
  const base = state.method === "buy" ? state.base_buy : state.base_roll;
  const classes = _readClasses();
  const deriv = computeDerivatives(state, classes);
  const conflicts = computeConflicts(state.totals, classes);
  const fmt = (n) => (n >= 0 ? `+${n}` : `${n}`);
  const incompleteRoll = state.method === "roll" && state.rolls && Object.keys(state.rollAssignments).length < 6;

  return (
    <aside className={`attr-panel ${collapsed ? "is-collapsed" : "is-expanded"}`} aria-label="Painel de atributos">
      {/* Handle vertical sempre visível */}
      <button
        className="ap-handle"
        onClick={() => setCollapsed((c) => !c)}
        aria-label={collapsed ? "Expandir painel de atributos" : "Recolher painel de atributos"}
        aria-expanded={!collapsed}
      >
        <span className="ap-handle-chevron">{collapsed ? "‹" : "›"}</span>
        <span className="ap-handle-label">Atributos</span>
        <span className="ap-handle-spacer" />
      </button>

      {/* Mini-rail quando colapsado: mostra os 6 totais em coluna */}
      {collapsed && (
        <div className="ap-rail">
          {ATTRS.map((a) => {
            const tone = state.totals[a] > 0 ? "pos" : state.totals[a] < 0 ? "neg" : "neutral";
            return (
              <button
                key={a}
                className={`ap-rail-cell ap-rail-cell--${tone}`}
                onClick={() => setCollapsed(false)}
                title={`${ATTR_LABELS[a]}: ${fmt(state.totals[a])}`}
              >
                <span className="ap-rail-attr">{a}</span>
                <span className="ap-rail-val">{fmt(state.totals[a])}</span>
              </button>
            );
          })}
          {(deriv.totalLevel > 0 || conflicts.length > 0) && (
            <div className="ap-rail-foot">
              {deriv.totalLevel > 0 && <span className="ap-rail-pill" title={`PV ${deriv.pv}`}>PV {deriv.pv}</span>}
              {conflicts.length > 0 && <span className="ap-rail-pill ap-rail-pill--warn" title="Conflitos">⚠ {conflicts.length}</span>}
            </div>
          )}
        </div>
      )}

      {/* Conteúdo expandido */}
      {!collapsed && (
        <div className="ap-body">
          <header className="ap-header">
            <span className="ap-header-eyebrow">Ficha</span>
            <span className="ap-header-title">Atributos</span>
            <span className="ap-header-meta">
              {deriv.totalLevel > 0 && <span title="Nível total">nv {deriv.totalLevel}</span>}
              {state.method === "buy"
                ? <span title="Método: compra">compra</span>
                : <span title="Método: rolagem">rolagem</span>}
            </span>
          </header>

          {incompleteRoll && (
            <div className="ap-warning" role="alert">
              <span>⚠</span> Atribuição de rolagens incompleta ({Object.keys(state.rollAssignments).length}/6)
            </div>
          )}

          <div className="ap-attrs">
            {ATTRS.map((a) => (
              <AttrCard
                key={a}
                attr={a}
                total={state.totals[a]}
                base={base[a]}
                raceMod={raceMods[a] || 0}
                originMod={originMods[a] || 0}
                method={state.method}
              />
            ))}
          </div>

          <div className="ap-divider"><span>·</span><span>·</span><span>·</span></div>

          <DerivativesSection deriv={deriv} totals={state.totals} />

          <ConflictsSection conflicts={conflicts} />

          <section className="ap-section ap-config">
            <button
              className="ap-section-head"
              onClick={() => setConfigOpen(!configOpen)}
              aria-expanded={configOpen}
            >
              <span className="ap-section-marker">✦</span>
              <span className="ap-section-title">Configurar base</span>
              <span className="ap-section-chevron">{configOpen ? "▾" : "▸"}</span>
            </button>
            {configOpen && (
              <div className="ap-config-body">
                <div className="ap-method-toggle" role="tablist">
                  <button
                    role="tab"
                    aria-selected={state.method === "buy"}
                    className={state.method === "buy" ? "is-active" : ""}
                    onClick={() => window.CHAR_ATTRS.setMethod("buy")}
                  >Compra</button>
                  <button
                    role="tab"
                    aria-selected={state.method === "roll"}
                    className={state.method === "roll" ? "is-active" : ""}
                    onClick={() => window.CHAR_ATTRS.setMethod("roll")}
                  >Rolagem</button>
                </div>
                {state.method === "buy"  && <BuyConfig state={state} />}
                {state.method === "roll" && <RollConfig state={state} />}
              </div>
            )}
          </section>
        </div>
      )}
    </aside>
  );
}

window.AttrPanel = AttrPanel;
