// ============================================================
// SCREEN 3 · SKILLS
// ============================================================

const ScreenSkills = ({ ch, setCh }) => {
  const { push } = useToast();
  if (!ch) return null;
  const [filter, setFilter] = useState('ALL');
  const abilityVal = (k) => (ch.abilityScores || {})[k] ?? (ABILITIES.find(a => a.k === k)?.v ?? 10);
  const [addingLang, setAddingLang] = useState(false);
  const [langInput, setLangInput] = useState('');
  const [addingTool, setAddingTool] = useState(false);
  const [toolInput, setToolInput] = useState('');
  const [addingWeapon, setAddingWeapon] = useState(false);
  const [weaponInput, setWeaponInput] = useState('');
  const doAddLang = () => {
    if (!langInput.trim()) return;
    setCh(c => ({ ...c, languages: [c.languages, langInput.trim()].filter(Boolean).join(', ') }));
    setLangInput(''); setAddingLang(false);
  };
  const doAddTool = () => {
    if (!toolInput.trim()) return;
    setCh(c => ({ ...c, toolProfs: [c.toolProfs, toolInput.trim()].filter(Boolean).join(', ') }));
    setToolInput(''); setAddingTool(false);
  };
  const doAddWeapon = () => {
    if (!weaponInput.trim()) return;
    setCh(c => ({ ...c, weaponProfs: [c.weaponProfs, weaponInput.trim()].filter(Boolean).join(', ') }));
    setWeaponInput(''); setAddingWeapon(false);
  };
  const removeLang   = (l) => setCh(c => ({ ...c, languages:   (c.languages   || '').split(',').map(x => x.trim()).filter(x => x && x !== l).join(', ') }));
  const removeTool   = (t) => setCh(c => ({ ...c, toolProfs:   (c.toolProfs   || '').split(',').map(x => x.trim()).filter(x => x && x !== t).join(', ') }));
  const removeWeapon = (w) => setCh(c => ({ ...c, weaponProfs: (c.weaponProfs || '').split(',').map(x => x.trim()).filter(x => x && x !== w).join(', ') }));

  // Per-character skill proficiency: 0 = none, 1 = prof, 2 = expertise
  const skillProf = (sk) => (ch.skillProfs || {})[sk.name] ?? sk.prof;
  const cycleSkillProf = (sk) => {
    const cur = skillProf(sk);
    const next = cur === 0 ? 1 : cur === 1 ? 2 : 0;
    setCh(c => ({ ...c, skillProfs: { ...(c.skillProfs || {}), [sk.name]: next } }));
  };

  const rollSkill = (sk) => {
    const p = skillProf(sk);
    const m = mod(abilityVal(sk.attr)) + (p === 1 ? ch.profBonus : p === 2 ? ch.profBonus * 2 : 0);
    const r = rollDie(20);
    push({ icon: 'dice', label: sk.name, value: r + m, detail: `d20[${r}] ${fmtMod(m)}`, crit: r === 20 });
  };

  const filtered = filter === 'ALL' ? SKILLS : SKILLS.filter(s => s.attr === filter);

  return (
    <>
      <SectionHeader
        title="Навички та умови"
        eyebrow="18 умінь · 6 рятівних кидків · мови та інструменти"
      />

      <div className="skills-grid">
        <Card title="Уміння"
          tag={`показано ${filtered.length}/${SKILLS.length}`}
          action={
            <div className="flex ig-4">
              <Chip onClick={() => setFilter('ALL')} active={filter === 'ALL'}>Усі</Chip>
              {ABILITIES.map(a => (
                <Chip key={a.k} onClick={() => setFilter(a.k)} active={filter === a.k}>{a.k}</Chip>
              ))}
            </div>
          }>
          <div className="skills-list">
            {filtered.map(sk => {
              const p = skillProf(sk);
              const m = mod(abilityVal(sk.attr)) + (p === 1 ? ch.profBonus : p === 2 ? ch.profBonus * 2 : 0);
              return (
                <div key={sk.name} className="skill-row" onClick={() => rollSkill(sk)}
                  data-tooltip={p === 2
                    ? `мод ${sk.attr} (${fmtMod(mod(abilityVal(sk.attr)))}) + проф×2 (${ch.profBonus*2})`
                    : p === 1
                    ? `мод ${sk.attr} (${fmtMod(mod(abilityVal(sk.attr)))}) + проф (${ch.profBonus})`
                    : `мод ${sk.attr} (${fmtMod(mod(abilityVal(sk.attr)))})`}>
                  {p === 2
                    ? <Dot expert title="Експертиза · клік → знизити" onClick={e => { e.stopPropagation(); cycleSkillProf(sk); }}/>
                    : <Dot on={p === 1} title={p === 1 ? 'Майстерність · клік → цикл' : 'Немає · клік → майстерність'} onClick={e => { e.stopPropagation(); cycleSkillProf(sk); }}/>}
                  <span className="igrow" style={{ fontWeight: 500 }}>{sk.name}</span>
                  <span className="mono" style={{ fontSize: 10, color: 'var(--ink-3)', width: 28 }}>{sk.attr}</span>
                  <span className="display" style={{ fontSize: 16, minWidth: 30, textAlign: 'right', color: m >= 0 ? 'var(--gold-1)' : 'var(--crimson-1)' }}>{fmtMod(m)}</span>
                </div>
              );
            })}
          </div>
          <div className="mono" style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 12 }}>
            ● майстерність · <span style={{ color: 'var(--violet-1)' }}>●</span> експертиза (×2 бонус)
          </div>
        </Card>

        <div className="icol ig-16">
          <Card title="Мови та інструменти">
            <div className="icol ig-12">
              <div>
                <div className="field-label mb-6">Мови</div>
                <div className="flex ig-6 iwrap">
                  {(ch.languages || '').split(',').map(l => l.trim()).filter(Boolean).map(l => (
                    <Chip key={l}>{l}<span onClick={() => removeLang(l)} style={{ marginLeft: 4, cursor: 'pointer', opacity: 0.5, lineHeight: 1 }}>×</span></Chip>
                  ))}
                  {addingLang ? (
                    <div className="flex ig-4 ica">
                      <input className="input" autoFocus placeholder="назва мови"
                        style={{ width: 120, height: 26, padding: '2px 8px', fontSize: 12 }}
                        value={langInput} onChange={e => setLangInput(e.target.value)}
                        onKeyDown={e => { if (e.key === 'Enter') doAddLang(); if (e.key === 'Escape') { setAddingLang(false); setLangInput(''); } }}/>
                      <IconBtn name="check" size={12} onClick={doAddLang}/>
                      <IconBtn name="x" size={12} onClick={() => { setAddingLang(false); setLangInput(''); }}/>
                    </div>
                  ) : (
                    <Chip muted onClick={() => setAddingLang(true)}>+ додати</Chip>
                  )}
                </div>
              </div>
              <div>
                <div className="field-label mb-6">Інструменти</div>
                <div className="flex ig-6 iwrap">
                  {(ch.toolProfs || '').split(',').map(t => t.trim()).filter(Boolean).map(t => (
                    <Chip key={t}>{t}<span onClick={() => removeTool(t)} style={{ marginLeft: 4, cursor: 'pointer', opacity: 0.5, lineHeight: 1 }}>×</span></Chip>
                  ))}
                  {addingTool ? (
                    <div className="flex ig-4 ica">
                      <input className="input" autoFocus placeholder="назва інструменту"
                        style={{ width: 140, height: 26, padding: '2px 8px', fontSize: 12 }}
                        value={toolInput} onChange={e => setToolInput(e.target.value)}
                        onKeyDown={e => { if (e.key === 'Enter') doAddTool(); if (e.key === 'Escape') { setAddingTool(false); setToolInput(''); } }}/>
                      <IconBtn name="check" size={12} onClick={doAddTool}/>
                      <IconBtn name="x" size={12} onClick={() => { setAddingTool(false); setToolInput(''); }}/>
                    </div>
                  ) : (
                    <Chip muted onClick={() => setAddingTool(true)}>+ додати</Chip>
                  )}
                </div>
              </div>
              <div>
                <div className="field-label mb-6">Зброя та броня</div>
                <div className="flex ig-6 iwrap">
                  {(ch.weaponProfs || '').split(',').map(w => w.trim()).filter(Boolean).map(w => (
                    <Chip key={w}>{w}<span onClick={() => removeWeapon(w)} style={{ marginLeft: 4, cursor: 'pointer', opacity: 0.5, lineHeight: 1 }}>×</span></Chip>
                  ))}
                  {addingWeapon ? (
                    <div className="flex ig-4 ica">
                      <input className="input" autoFocus placeholder="зброя або тип броні"
                        style={{ width: 150, height: 26, padding: '2px 8px', fontSize: 12 }}
                        value={weaponInput} onChange={e => setWeaponInput(e.target.value)}
                        onKeyDown={e => { if (e.key === 'Enter') doAddWeapon(); if (e.key === 'Escape') { setAddingWeapon(false); setWeaponInput(''); } }}/>
                      <IconBtn name="check" size={12} onClick={doAddWeapon}/>
                      <IconBtn name="x" size={12} onClick={() => { setAddingWeapon(false); setWeaponInput(''); }}/>
                    </div>
                  ) : (
                    <Chip muted onClick={() => setAddingWeapon(true)}>+ додати</Chip>
                  )}
                </div>
              </div>
            </div>
          </Card>

          <Card title="Інші переваги">
            <div className="icol ig-8">
              {[...(FEATURES_RACE || []), ...(FEATURES_FEATS || [])].map((f, i) => (
                <div key={i} className="flex ica ig-8">
                  <Icon name="check" size={14} style={{ color: 'var(--emerald-1)' }}/>
                  <span className="igrow" title={f.desc}>{f.name}</span>
                  <span className="mono dim">{f.src || '—'}</span>
                </div>
              ))}
              {([...(FEATURES_RACE || []), ...(FEATURES_FEATS || [])].length === 0) && (
                <span className="mono dim" style={{ fontSize: 12 }}>Немає додаткових переваг</span>
              )}
            </div>
          </Card>
        </div>
      </div>
    </>
  );
};

// ============================================================
// SCREEN 4 · SPELLS
// ============================================================

const ScreenSpells = ({ ch, setCh }) => {
  const { push } = useToast();
  if (!ch) return null;
  const _defaultSlots = SPELLS_GROUPED.reduce((acc, g) => {
    if (g.slots) acc[g.level] = { ...g.slots };
    return acc;
  }, {});
  const slots = (ch.spellSlots && Object.keys(ch.spellSlots).length) ? ch.spellSlots : _defaultSlots;
  const setSlots = (updater) => setCh(c => ({
    ...c,
    spellSlots: typeof updater === 'function'
      ? updater((c.spellSlots && Object.keys(c.spellSlots).length) ? c.spellSlots : _defaultSlots)
      : updater,
  }));
  // ── Доменні слоти (Клірик та ін.) ──
  const domainSlots = ch.domainSlots || {};
  const setDomainSlots = (updater) => setCh(c => ({
    ...c,
    domainSlots: typeof updater === 'function' ? updater(c.domainSlots || {}) : updater,
  }));
  const isDomain = (sp) => sp.domain === true || (sp.desc || '').includes('[ДОМЕН]');
  const domainTierLabel = (tier) =>
    +tier === 1 ? 'Рів. 1–2' : +tier === 3 ? 'Рів. 3–4' :
    +tier === 5 ? 'Рів. 5–6' : +tier === 7 ? 'Рів. 7–8' : 'Рів. 9';
  const tierForLevel = (level) => level <= 2 ? 1 : level <= 4 ? 3 : level <= 6 ? 5 : level <= 8 ? 7 : 9;
  const toggleDomainSlot = (tier) => {
    setDomainSlots(s => {
      const cur = s[tier] || { used: 0, total: 1 };
      const used = cur.used < cur.total ? cur.used + 1 : 0;
      return { ...s, [tier]: { ...cur, used } };
    });
  };
  const addDomainTier = (tier) => {
    setDomainSlots(s => ({ ...s, [tier]: { used: 0, total: 1 } }));
  };
  const removeDomainTier = (tier) => {
    setDomainSlots(s => { const n = { ...s }; delete n[tier]; return n; });
  };

  const [search, setSearch] = useState('');
  const [activeSpell, setActiveSpell] = useState(null);
  const [castableOnly, setCastableOnly] = useState(false);
  const [editingDesc, setEditingDesc] = useState(false);
  const [descDraft, setDescDraft] = useState('');
  const [addSpellOpen, setAddSpellOpen] = useState(false);
  const [newSpell, setNewSpell] = useState({ name: '', level: 0, school: '', dmg: '', desc: '' });

  const toggleSlot = (level, idx) => {
    setSlots(s => {
      const cur = s[level];
      const used = idx < (cur.total - cur.used) ? cur.used + 1 : cur.used - 1;
      return { ...s, [level]: { ...cur, used: Math.max(0, Math.min(cur.total, used)) } };
    });
  };

  // Підготовлені закляття — per-character override поверх статичних даних
  const isSpellPrepared = (sp, level) => {
    if (level === 0) return true; // трюки завжди підготовлені
    const override = ch.spellPrepared?.[sp.name];
    if (override !== undefined) return override;
    return sp.prepared ?? false;
  };
  const toggleSpellPrepared = (sp, level) => {
    if (level === 0) return;
    setCh(c => {
      const cur = isSpellPrepared(sp, level);
      return { ...c, spellPrepared: { ...(c.spellPrepared || {}), [sp.name]: !cur } };
    });
  };

  const castSpell = (sp, level) => {
    const isDomain = sp.desc && sp.desc.includes('[ДОМЕН]');
    if (isDomain && level > 0) {
      const tier = [9, 7, 5, 3, 1].find(t => level >= t) || 1;
      const cur = domainSlots[tier];
      if (cur && cur.used < cur.total) {
        setDomainSlots(s => ({ ...s, [tier]: { ...s[tier], used: s[tier].used + 1 } }));
      }
    } else if (level > 0) {
      setSlots(s => {
        const slot = s[level];
        if (!slot || slot.used >= slot.total) return s;
        return { ...s, [level]: { ...slot, used: slot.used + 1 } };
      });
    }
    push({ icon: 'spell', label: `Каст: ${sp.name}`, detail: sp.school + ' · ' + (sp.range || '') });
  };

  const longRest = () => {
    if (!window.confirm('Тривалий відпочинок? HP, слоти та стани будуть відновлені.')) return;
    setCh(c => ({
      ...c,
      hp: c.hpMax,
      hpTemp: 0,
      deathSuccess: 0,
      deathFail: 0,
      conditions: {},
      hitDiceUsed: 0,
      featureCharges: {},
      actionUsed: { action: false, bonus: false, reaction: false },
      spellSlots: Object.fromEntries(Object.entries(c.spellSlots || {}).map(([l, v]) => [l, { ...v, used: 0 }])),
      domainSlots: Object.fromEntries(Object.entries(c.domainSlots || {}).map(([t, v]) => [t, { ...v, used: 0 }])),
      combatState: { ...(c.combatState || {}), concSpell: null, round: 1, activeInitName: null, extraInit: [], playerRolledInit: null },
    }));
    push({ icon: 'moon', label: 'Тривалий відпочинок', detail: 'HP, слоти, доменні слоти, здібності та стани очищено' });
  };

  const hasSpells = SPELLS_GROUPED.length > 0
    || (ch.customSpells || []).length > 0
    || Object.keys(ch.spellSlots || {}).length > 0;

  if (!hasSpells) {
    return (
      <>
        <SectionHeader title="Магія" eyebrow="цей персонаж не є заклинателем"/>
        <Card>
          <div className="flex ica ig-12" style={{ padding: '32px 0', justifyContent: 'center', color: 'var(--ink-3)' }}>
            <Icon name="spell" size={24}/>
            <span className="display" style={{ fontSize: 15 }}>Цей персонаж не є заклинателем</span>
          </div>
        </Card>
      </>
    );
  }

  return (
    <>
      <SectionHeader
        title="Магія"
        eyebrow={`${ch.spellAbility} · СК ${ch.spellSaveDC} · атака +${ch.spellAttack}`}
        action={
          <Btn primary sm onClick={longRest}><Icon name="moon" size={12}/> Тривалий відпочинок</Btn>
        }
      />

      {/* Spellcasting stats strip */}
      <div className="spell-stats-strip mb-16">
        <div className="spell-stat-tile">
          <div className="caps mono dim" style={{ fontSize: 9 }}>Магічна хар.</div>
          <div className="display" style={{ fontSize: 28 }}>{ch.spellAbility}</div>
        </div>
        <div className="spell-stat-tile" data-tooltip={`проф (${ch.profBonus}) + мод ${ch.spellAbility} (${Math.floor(((ch.abilityScores?.[ch.spellAbility] ?? 10)-10)/2) >= 0 ? '+' : ''}${Math.floor(((ch.abilityScores?.[ch.spellAbility] ?? 10)-10)/2)})`}>
          <div className="caps mono dim" style={{ fontSize: 9 }}>Атака закл.</div>
          <div className="display" style={{ fontSize: 28, color: 'var(--gold-1)' }}>+{ch.spellAttack}</div>
        </div>
        <div className="spell-stat-tile" data-tooltip={`8 + проф (${ch.profBonus}) + мод ${ch.spellAbility} (${Math.floor(((ch.abilityScores?.[ch.spellAbility] ?? 10)-10)/2) >= 0 ? '+' : ''}${Math.floor(((ch.abilityScores?.[ch.spellAbility] ?? 10)-10)/2)})`}>
          <div className="caps mono dim" style={{ fontSize: 9 }}>СК ряткуба</div>
          <div className="display" style={{ fontSize: 28, color: 'var(--gold-1)' }}>{ch.spellSaveDC}</div>
        </div>
        <div className="spell-stat-tile">
          <div className="caps mono dim" style={{ fontSize: 9 }}>Підготовлено</div>
          <div className="display" style={{ fontSize: 28 }}>{ch.preparedKnown}<span style={{ fontSize: 14, color: 'var(--ink-3)' }}> / {ch.preparedMax}</span></div>
        </div>
      </div>

      <div className="spells-grid">
        {/* SLOTS */}
        <div className="icol ig-16">
          <Card title="Слоти заклять" tag="клік → витратити">
            {/* Cantrips row — always infinite */}
            {(ch.customSpells || []).some(s => s.level === 0) && (
              <div className="slot-row">
                <div className="slot-row-label">Трюки</div>
                <div className="slot-row-dots">
                  <span className="display" style={{ fontSize: 18, color: 'var(--violet-1)' }}>∞</span>
                </div>
              </div>
            )}
            {/* Per-character spell slots (when no global SPELLS_GROUPED) */}
            {!SPELLS_GROUPED.length && Object.entries(ch.spellSlots || {})
              .sort(([a], [b]) => +a - +b)
              .filter(([, slotData]) => slotData && typeof slotData === 'object')
              .map(([lvl, slotData]) => {
                const level = +lvl;
                const used  = slotData.used || 0;
                return (
                  <div key={level} className="slot-row">
                    <div className="slot-row-label">Рів. {level}</div>
                    <div className="slot-row-dots">
                      {Array.from({ length: slotData.total }).map((_, i) => {
                        const full = i < (slotData.total - used);
                        return <Slot key={i} full={full} used={!full} onClick={() => toggleSlot(level, i)}/>;
                      })}
                    </div>
                    <div className="mono dim" style={{ fontSize: 10 }}>
                      {slotData.total - used} / {slotData.total}
                    </div>
                  </div>
                );
              })
            }
            {/* Global SPELLS_GROUPED slots (e.g. Фінрад if he had spells) */}
            {SPELLS_GROUPED.map(g => (
              <div key={g.level} className="slot-row">
                <div className="slot-row-label">{g.level === 0 ? 'Трюки' : `Рів. ${g.level}`}</div>
                <div className="slot-row-dots">
                  {g.level === 0 ? (
                    <span className="display" style={{ fontSize: 18, color: 'var(--violet-1)' }}>∞</span>
                  ) : g.slots ? (
                    Array.from({ length: g.slots.total }).map((_, i) => {
                      const full = i < (g.slots.total - (slots[g.level]?.used || 0));
                      return <Slot key={i} full={full} used={!full} onClick={() => toggleSlot(g.level, i)}/>;
                    })
                  ) : (
                    <span className="mono dim">—</span>
                  )}
                </div>
                {g.slots && (
                  <div className="mono dim" style={{ fontSize: 10 }}>
                    {(slots[g.level]?.total || 0) - (slots[g.level]?.used || 0)} / {slots[g.level]?.total || 0}
                  </div>
                )}
              </div>
            ))}

            {/* ── Доменні слоти (Клірик) ── */}
            {(Object.keys(domainSlots).length > 0 || ch.klass === 'Клірик' || ch.klass === 'Жрець') && (
              <>
                <Divider/>
                <div className="flex iba ig-6" style={{ marginBottom: 8 }}>
                  <span className="caps mono" style={{ fontSize: 9, color: 'var(--azure-1)', letterSpacing: '0.12em' }}>
                    ДОМЕННІ СЛОТИ
                  </span>
                  <div className="igrow"/>
                  {/* Кнопка додати наступний незайнятий тір */}
                  {(() => {
                    const TIERS = [1, 3, 5, 7, 9];
                    const next = TIERS.find(t => !domainSlots[t]);
                    return next ? (
                      <Btn ghost sm onClick={() => addDomainTier(next)}>
                        <Icon name="plus" size={10}/> {domainTierLabel(next)}
                      </Btn>
                    ) : null;
                  })()}
                </div>
                {Object.keys(domainSlots).length === 0 && (
                  <div className="mono dim" style={{ fontSize: 11, padding: '4px 0' }}>
                    Немає доменних слотів — натисни «+»
                  </div>
                )}
                {Object.entries(domainSlots).sort(([a], [b]) => +a - +b).filter(([, slot]) => slot && typeof slot === 'object').map(([tier, slot]) => (
                  <div key={tier} className="slot-row">
                    <div className="slot-row-label" style={{ color: 'var(--azure-1)' }}>
                      {domainTierLabel(tier)}
                    </div>
                    <div className="slot-row-dots">
                      <Slot full={slot.used < slot.total} used={slot.used >= slot.total}
                        onClick={() => toggleDomainSlot(+tier)}/>
                    </div>
                    <div className="mono" style={{ fontSize: 10, color: 'var(--azure-1)' }}>
                      {slot.total - slot.used} / {slot.total}
                    </div>
                    <IconBtn name="trash" size={10} title="Прибрати тір"
                      onClick={() => removeDomainTier(+tier)}/>
                  </div>
                ))}
              </>
            )}
          </Card>

          {/* Schools filter */}
          {(() => {
            const schoolCounts = {};
            SPELLS_GROUPED.forEach(g => g.spells.forEach(sp => {
              if (sp.school) schoolCounts[sp.school] = (schoolCounts[sp.school] || 0) + 1;
            }));
            (ch.customSpells || []).forEach(sp => {
              if (sp.school) schoolCounts[sp.school] = (schoolCounts[sp.school] || 0) + 1;
            });
            const totalSpells = Object.values(schoolCounts).reduce((a, b) => a + b, 0);
            const SCHOOLS = ['Виклик','Перетворення','Прорицання','Зачарування','Некромантія','Ілюзія','Руйнування','Відновлення'];
            return (
              <Card title="Школи" tag={`${totalSpells} закляттів`}>
                {SCHOOLS.map(s => {
                  const count = schoolCounts[s] || 0;
                  return (
                    <div key={s} className="flex ica ig-8" style={{ padding: '5px 0', opacity: count ? 1 : 0.4 }}>
                      <Dot on={count > 0}/>
                      <span className="igrow" style={{ fontSize: 12 }}>{s}</span>
                      <span className="mono dim" style={{ fontSize: 10 }}>{count || '—'}</span>
                    </div>
                  );
                })}
              </Card>
            );
          })()}
        </div>

        {/* SPELL LIST */}
        <Card title="Закляття" tag={`${ch.preparedKnown}/${ch.preparedMax} підготовлено`}
          action={
            <div className="flex ig-6 ica">
              <Chip active={castableOnly} onClick={() => setCastableOnly(v => !v)} title="Тільки закляття, які можна кастувати зараз">
                <Icon name="bolt" size={10}/> Що я можу?
              </Chip>
              <div className="search-mini">
                <Icon name="search" size={12}/>
                <input placeholder="пошук закляття…" value={search} onChange={(e) => setSearch(e.target.value)}/>
              </div>
              <Btn primary sm onClick={() => setAddSpellOpen(true)}><Icon name="plus" size={11}/></Btn>
            </div>
          }>
          {castableOnly && (
            <div className="filter-banner mb-12">
              <Icon name="bolt" size={12}/>
              <span>Показано тільки <b>підготовлені</b> закляття, для яких є <b>вільний слот</b>.</span>
              <span className="igrow"/>
              <button onClick={() => setCastableOnly(false)} className="filter-banner-clear" title="Скинути фільтр">
                <Icon name="x" size={11}/>
              </button>
            </div>
          )}
          {SPELLS_GROUPED.map(g => {
            const slotsFree = g.slots ? (slots[g.level]?.total || 0) - (slots[g.level]?.used || 0) : Infinity;
            const list = g.spells.filter(s => {
              if (search && !s.name.toLowerCase().includes(search.toLowerCase())) return false;
              if (castableOnly) {
                if (g.level > 0 && slotsFree <= 0) return false;
                if (!isSpellPrepared(s, g.level)) return false;
              }
              return true;
            });
            if (!list.length) return null;
            return (
              <div key={g.level} className="spell-group">
                <div className="spell-group-h">
                  <span className="display" style={{ fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--gold-1)' }}>{g.title}</span>
                  <div className="igrow"/>
                  {g.slots && (
                    <span className="mono dim" style={{ fontSize: 10 }}>
                      {slotsFree} / {slots[g.level]?.total || 0} слотів
                    </span>
                  )}
                </div>
                {list.map((sp, i) => {
                  const prepared = isSpellPrepared(sp, g.level);
                  return (
                  <div key={i} className={`spell-row ${sp.signature ? 'signature' : ''}`}
                    style={{ opacity: prepared || g.level === 0 ? 1 : 0.45 }}
                    onClick={() => setActiveSpell({ ...sp, level: g.level })}>
                    <Dot on={prepared}
                      title={g.level === 0 ? 'Трюк (завжди підготовлений)' : prepared ? 'Підготовлено (клік → зняти)' : 'Не підготовлено (клік → підготувати)'}
                      onClick={e => { e.stopPropagation(); toggleSpellPrepared(sp, g.level); }}/>
                    <div className="igrow">
                      <div className="flex ica ig-6">
                        <span style={{ fontWeight: 600 }}>{sp.name}</span>
                        {sp.signature && <Icon name="star" size={11} style={{ color: 'var(--gold-1)' }}/>}
                        {sp.reaction && <Chip sm color="azure" fill>Реакція</Chip>}
                      </div>
                      <div className="mono dim" style={{ fontSize: 10, marginTop: 2 }}>
                        {sp.school} · {sp.range} · {sp.comp} · {sp.dur}
                      </div>
                    </div>
                    {sp.atk && <span className="display gold" style={{ fontSize: 14, minWidth: 36 }}>{sp.atk}</span>}
                    {sp.dmg && <span className="mono" style={{ fontSize: 12, minWidth: 70, color: 'var(--ink-1)' }}>{sp.dmg}</span>}
                    {sp.save && <span className="mono dim" style={{ fontSize: 10, minWidth: 50 }}>{sp.save}</span>}
                    <Btn sm disabled={g.level > 0 && !prepared}
                      onClick={(e) => { e.stopPropagation(); castSpell(sp, g.level); }}>каст</Btn>
                  </div>
                  );
                })}
              </div>
            );
          })}
          {/* Власні закляття */}
          {(ch.customSpells || []).length > 0 && (
            <div className="spell-group">
              <div className="spell-group-h">
                <span className="display" style={{ fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--gold-1)' }}>Власні</span>
                <div className="igrow"/>
                <span className="mono dim" style={{ fontSize: 10 }}>{ch.customSpells.length}</span>
              </div>
              {ch.customSpells.map((sp, i) => (
                <div key={i} className="spell-row" onClick={() => setActiveSpell({ ...sp, comp: '—', dur: '—', range: '—', atk: null })}>
                  <Dot on/>
                  <div className="igrow">
                    <div className="flex ica ig-6">
                      <span style={{ fontWeight: 600 }}>{sp.name}</span>
                      {isDomain(sp) && (
                        <Chip sm color="azure" fill
                          title={`Доменне · тір ${domainTierLabel(tierForLevel(sp.level || 1))}`}>
                          домен
                        </Chip>
                      )}
                    </div>
                    <div className="mono dim" style={{ fontSize: 10, marginTop: 2 }}>
                      {sp.school || '—'} · {sp.level === 0 ? 'трюк' : `рів. ${sp.level}`}
                    </div>
                  </div>
                  {sp.dmg && <span className="mono" style={{ fontSize: 12, color: 'var(--ink-1)' }}>{sp.dmg}</span>}
                  <Btn sm onClick={e => { e.stopPropagation(); castSpell(sp, sp.level || 0); }}>каст</Btn>
                  <IconBtn name="trash" size={12} title="Видалити" onClick={e => {
                    e.stopPropagation();
                    setCh(c => ({
                      ...c,
                      customSpells: (c.customSpells || []).filter((_, j) => j !== i),
                      spellNotes: Object.fromEntries(Object.entries(c.spellNotes || {}).filter(([k]) => k !== sp.name)),
                    }));
                    push({ label: `Видалено: ${sp.name}` });
                  }}/>
                </div>
              ))}
            </div>
          )}
        </Card>
      </div>

      {/* ===== MODAL: Додати закляття ===== */}
      {addSpellOpen && (
        <div className="dice-overlay-backdrop" onClick={() => setAddSpellOpen(false)}>
          <div className="dice-overlay" style={{ width: 480, minHeight: '62vh' }} onClick={e => e.stopPropagation()}>
            <div className="dice-overlay-head">
              <Icon name="spell" size={18} style={{ color: 'var(--gold-1)' }}/>
              <h2>Нове закляття</h2>
              <div className="igrow"/>
              <IconBtn name="x" onClick={() => setAddSpellOpen(false)}/>
            </div>
            <div className="dice-overlay-body" style={{ display: 'flex', flexDirection: 'column' }}>
              <div style={{ display: 'flex', flexDirection: 'column', flex: 1, gap: 12, minHeight: 0 }}>
                <div className="flex ig-8">
                  <div className="igrow">
                    <div className="field-label mb-4">Назва *</div>
                    <input className="input" autoFocus placeholder="Вогняна куля"
                      value={newSpell.name} onChange={e => setNewSpell(v => ({ ...v, name: e.target.value }))}/>
                  </div>
                  <div style={{ width: 88 }}>
                    <div className="field-label mb-4">Рівень (0=трюк)</div>
                    <input className="input" type="number" min="0" max="9" value={newSpell.level}
                      onChange={e => setNewSpell(v => ({ ...v, level: Math.max(0, Math.min(9, Number(e.target.value) || 0)) }))}/>
                  </div>
                </div>
                <div className="flex ig-8">
                  <div className="igrow">
                    <div className="field-label mb-4">Школа</div>
                    <input className="input" placeholder="Виклик, Руйнування…"
                      value={newSpell.school} onChange={e => setNewSpell(v => ({ ...v, school: e.target.value }))}/>
                  </div>
                  <div className="igrow">
                    <div className="field-label mb-4">Дамаг / ефект</div>
                    <input className="input" placeholder="8d6 вогнем"
                      value={newSpell.dmg} onChange={e => setNewSpell(v => ({ ...v, dmg: e.target.value }))}/>
                  </div>
                </div>
                <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
                  <div className="field-label mb-4">Опис</div>
                  <textarea className="textarea" style={{ flex: 1, resize: 'none', minHeight: 80 }}
                    placeholder="Опис ефекту…"
                    value={newSpell.desc} onChange={e => setNewSpell(v => ({ ...v, desc: e.target.value }))}/>
                </div>
                <div className="flex ig-8">
                  <Btn primary onClick={() => {
                    if (!newSpell.name.trim()) return;
                    const sp = { ...newSpell, name: newSpell.name.trim() };
                    setCh(c => ({
                      ...c,
                      customSpells: [...(c.customSpells || []), sp],
                      spellNotes: { ...(c.spellNotes || {}), [sp.name]: sp.desc },
                    }));
                    setNewSpell({ name: '', level: 0, school: '', dmg: '', desc: '' });
                    setAddSpellOpen(false);
                  }}><Icon name="plus" size={12}/> Додати</Btn>
                  <Btn ghost onClick={() => setAddSpellOpen(false)}>Скасувати</Btn>
                </div>
              </div>
            </div>
          </div>
        </div>
      )}

      {/* Quick spell detail modal */}
      {activeSpell && (
        <div className="dice-overlay-backdrop" onClick={() => { setActiveSpell(null); setEditingDesc(false); }}>
          <div className="dice-overlay" style={{ width: 540, minHeight: '62vh' }} onClick={(e) => e.stopPropagation()}>
            <div className="dice-overlay-head">
              <Icon name="spell" size={20} style={{ color: 'var(--gold-1)' }}/>
              <h2>{activeSpell.name}</h2>
              <div className="igrow"/>
              <IconBtn name="x" onClick={() => { setActiveSpell(null); setEditingDesc(false); }}/>
            </div>
            <div className="dice-overlay-body">
              <div className="flex iwrap ig-6 mb-12">
                <Chip color="violet" fill>{activeSpell.level === 0 ? 'Трюк' : `Рівень ${activeSpell.level}`}</Chip>
                <Chip>{activeSpell.school}</Chip>
                <Chip muted>{activeSpell.range}</Chip>
                <Chip muted>{activeSpell.comp}</Chip>
                <Chip muted>{activeSpell.dur}</Chip>
              </div>
              {editingDesc ? (
                <div className="icol ig-8">
                  <textarea className="textarea" rows="6" autoFocus
                    value={descDraft} onChange={e => setDescDraft(e.target.value)}/>
                  <div className="flex ig-8">
                    <Btn primary sm onClick={() => {
                      setCh(c => ({ ...c, spellNotes: { ...(c.spellNotes||{}), [activeSpell.name]: descDraft } }));
                      setEditingDesc(false);
                    }}><Icon name="check" size={11}/> Зберегти</Btn>
                    <Btn ghost sm onClick={() => setEditingDesc(false)}>Скасувати</Btn>
                  </div>
                </div>
              ) : (
                <div style={{ fontFamily: 'var(--font-script)', fontSize: 16, lineHeight: 1.6, color: 'var(--ink-1)' }}>
                  <p>{
                    ch.spellNotes?.[activeSpell.name] ||
                    (activeSpell.name === 'Вогняна куля'
                      ? 'Яскрава вогняна смуга летить з вашого пальця у точку в межах 150 футів. Кожна істота у сфері радіусом 20 футів робить ряткуб СПР. При провалі — 8d6 вогню; при успіху — половина.'
                      : 'Опис закляття. Клікни «ред.» щоб додати свій.')
                  }</p>
                </div>
              )}
              <Divider gold/>
              <div className="flex ig-8">
                <Btn primary onClick={() => { castSpell(activeSpell, activeSpell.level); setActiveSpell(null); }}>
                  <Icon name="spell" size={14}/> Каст
                </Btn>
                {activeSpell.atk && (
                  <Btn onClick={() => {
                    const bonus = parseInt((activeSpell.atk || '+0').replace(/^\+/, ''), 10) || 0;
                    const r = rollDie(20);
                    push({ icon: 'dice', label: `${activeSpell.name} (атака)`, value: r + bonus,
                      detail: `d20[${r}] ${activeSpell.atk}`, crit: r === 20 });
                    setActiveSpell(null);
                  }}><Icon name="dice" size={12}/> Атака {activeSpell.atk}</Btn>
                )}
                {activeSpell.dmg && (
                  <Btn danger onClick={() => {
                    const m = (activeSpell.dmg || '').match(/^(\d+)d(\d+)([+-]\d+)?/i);
                    if (m) {
                      const rolls = rollDice(parseInt(m[1], 10), parseInt(m[2], 10));
                      const bonus = parseInt(m[3] || '0', 10);
                      const total = rolls.reduce((a, b) => a + b, 0) + bonus;
                      push({ icon: 'flame', label: `${activeSpell.name} (дамаг)`, value: total,
                        detail: `[${rolls.join('+')}]${bonus ? (bonus > 0 ? '+' : '') + bonus : ''} · ${activeSpell.school || ''}` });
                    } else {
                      push({ icon: 'flame', label: `${activeSpell.name} (дамаг)`, value: activeSpell.dmg });
                    }
                    setActiveSpell(null);
                  }}><Icon name="flame" size={12}/> {activeSpell.dmg}</Btn>
                )}
                <div className="igrow"/>
                <Btn ghost sm onClick={() => { setDescDraft(ch.spellNotes?.[activeSpell.name] || ''); setEditingDesc(true); }}>
                  <Icon name="edit" size={11}/> ред.
                </Btn>
              </div>
            </div>
          </div>
        </div>
      )}
    </>
  );
};

window.ScreenSkills = ScreenSkills;
window.ScreenSpells = ScreenSpells;
