/* ============================================================
   ATTACK RESOLVER — guided attack sequence
   1 target  →  2 gather+roll  →  3 modify (accuracy/CF/reroll)
   →  4 defense tokens  →  5 resolve damage
   ============================================================ */
const { useState: useStateAR, useMemo: useMemoAR } = React;

// welke hull zones van `t` mag `attacker` deze activatie nog vanuit `arc` beschieten?
// (same-hull-zone-regel; Gunnery Team heft die op)
function freeZonesFor(attacker, arc, t) {
  const zones = targetableZones(attacker, arc, t, BASE_SIZES);
  if (upgradeAllowsSameZone(attacker)) return zones;
  const used = attacker.attackedZones || [];
  return zones.filter((z) => !used.includes(t.id + ":" + z));
}

function targetsFor(attacker, ships, arc, blockLongRange) {
  return ships.filter((s) => s.id !== attacker.id && !s.destroyed && s.faction !== attacker.faction).map((t) => {
    let range = rangeBetween(attacker, t);
    // depowered_armament (schadekaart): geen aanval op long range meer mogelijk
    if (blockLongRange && range === "long") range = null;
    // legaliteit vanaf de fysieke rand van de verdedigende hull zone (zie hull-zones.js),
    // niet vanaf een peiling middelpunt→middelpunt
    const zones = targetableZones(attacker, arc, t, BASE_SIZES);
    const freeZones = freeZonesFor(attacker, arc, t);
    const seenFrom = firingArcsAgainst(attacker, t, BASE_SIZES);
    return {
      ship: t, range, zones, freeZones, seenFrom,
      inArc: zones.length > 0,
      zoneUsed: zones.length > 0 && freeZones.length === 0,
      hitArc: nearestZone(attacker, t, freeZones.length ? freeZones : zones, BASE_SIZES),
    };
  });
}

// verwijdert 1 die (hoogste kleur-count eerst) uit een dice-pool — voor damaged_munitions/point_defense_failure
function removeOneDie(pool) {
  const out = { ...pool };
  let best = null, bestN = -1;
  ["red", "blue", "black"].forEach((c) => { if ((out[c] || 0) > bestN) { bestN = out[c] || 0; best = c; } });
  if (best && out[best] > 0) out[best] -= 1;
  return out;
}

const EMPTY_DEFOM = { braceId: null, scatterId: null, containId: null, redirectTokenId: null, redirectArc: null, evadeTokenIds: [], evadeDieIds: [] };
// stap 2 (hull zone kiezen) hoort visueel bij "Doel" en krijgt dus geen eigen bolletje
const AR_STEP_IX = { 1: 0, 2: 0, 3: 1, 4: 2, 6: 3 };

function AttackResolver({ attacker, ships, squadrons, obstacles, useCF, onApply, onApplySquadron, onClose, addLog }) {
  const [step, setStep] = useStateAR(1);
  const [mode, setMode] = useStateAR("ship");                 // "ship" | "squadron" (anti-squadron armament)
  const [arc, setArc] = useStateAR("front");
  const [targetId, setTargetId] = useStateAR(null);
  const [hitZone, setHitZone] = useStateAR(null);             // gekozen verdedigende hull zone
  const [range, setRange] = useStateAR(null);
  const [dice, setDice] = useStateAR([]);
  const [obstructed, setObstructed] = useStateAR(false);       // line-of-sight geblokkeerd door obstakel → -1 die
  const [spentAccuracy, setSpentAccuracy] = useStateAR({});   // tokenId -> true (locked, alleen deze aanval)
  const [rerollsLeft, setRerollsLeft] = useStateAR(0);
  const [cfColor, setCfColor] = useStateAR(null);             // null = auto
  const [cfAdded, setCfAdded] = useStateAR(null);             // welke kleur CF toevoegde
  const [defom, setDefom] = useStateAR(EMPTY_DEFOM);
  const [applied, setApplied] = useStateAR(null);
  const [intelTokenId, setIntelTokenId] = useStateAR(null);   // Intel Officer: gemerkt token
  const [nk7TokenId, setNk7TokenId] = useStateAR(null);       // NK-7: token dat de verdediger afgooit

  const target = mode === "ship" ? (ships.find((s) => s.id === targetId) || null) : null;
  const fa = attacker;

  // valid arcs that actually have dice
  const arcsWithDice = ARCS.filter((a) => poolSize(fa.attackDice[a]) > 0 && !fa.arcsFired.includes(a));
  // schadekaart-effecten van de aanvaller (fa) — zie src/damage-effects.js
  const noAccuracy = critNoAccuracy(fa);
  const noLongRange = critNoLongRange(fa);
  const noCritResolve = critNoCritResolve(fa);
  const removeDieVsShip = critRemoveDieVsShip(fa);
  const removeDieVsSquad = critRemoveDieVsSquad(fa);
  const candidates = useMemoAR(() => target ? null : targetsFor(fa, ships, arc, noLongRange), [fa, ships, arc, target, noLongRange]);
  // stap 2: welke hull zones van het gekozen doel zijn nog legaal vanuit deze arc?
  const zoneChoices = useMemoAR(() => (target ? freeZonesFor(fa, arc, target) : []), [fa, arc, target]);

  // doel gekozen: één legale zone → meteen rollen, meerdere → de speler laten kiezen
  const pickTarget = (t, freeZones) => {
    if (!freeZones || !freeZones.length) return;
    if (freeZones.length === 1) { beginRoll(t, freeZones[0]); return; }
    setTargetId(t.id); setRange(rangeBetween(fa, t)); setStep(2);
  };

  // ---- anti-squadron armament: ship vs squadron, close range only ----
  const hasAntiSquadron = poolSize(fa.antiSquadron || { red: 0, blue: 0, black: 0 }) > 0;
  const sqCandidates = useMemoAR(() => {
    if (!hasAntiSquadron || !squadrons) return [];
    return squadrons.filter((q) => !q.destroyed && q.faction !== fa.faction &&
      rangeBetween(fa, q) === "close" && !(fa.attackedZones || []).includes(q.id));
  }, [squadrons, fa, hasAntiSquadron]);
  const sqTarget = mode === "squadron" ? ((squadrons || []).find((q) => q.id === targetId) || null) : null;
  const sqDefTokens = sqTarget ? sqTarget.defenseTokens.filter((dt) => dt.state === "ready" && (dt.type === "Scatter" || dt.type === "Brace")) : [];

  const beginSqRoll = (sq) => {
    if ((fa.attackedZones || []).includes(sq.id)) return;
    setMode("squadron");
    setTargetId(sq.id);
    setRange("close");
    // point_defense_failure (schadekaart): -1 die vóór het rollen, bij aanval op een squadron
    let antiSqPool = removeDieVsSquad ? removeOneDie(fa.antiSquadron) : fa.antiSquadron;
    // obstakel op de aanvalslijn: -1 die (line-of-sight geobstrueerd)
    const losBlocked = attackObstructed(fa, sq, obstacles);
    if (losBlocked) antiSqPool = removeOneDie(antiSqPool);
    setObstructed(losBlocked);
    setDice(rollPool(antiSqPool));
    setRerollsLeft(0);
    setDefom(EMPTY_DEFOM);
    setStep(3);
  };

  const beginRoll = (t, zone) => {
    let r = rangeBetween(fa, t);
    if (!r || !zone) return;
    // depowered_armament (schadekaart): geen aanval op long range meer mogelijk
    if (noLongRange && r === "long") return;
    if (fa.arcsFired.includes(arc)) return; // deze arc is deze activatie al gebruikt
    setTargetId(t.id); setRange(r); setHitZone(zone);
    let p = gatherArcDice(fa, arc, r);
    // damaged_munitions (schadekaart): -1 die vóór het rollen, bij aanval op een schip
    if (removeDieVsShip) p = removeOneDie(p);
    // obstakel op de line of sight: van de vurende rand naar de verdedigende rand
    const los = losLine(fa, arc, t, zone, BASE_SIZES);
    const losBlocked = attackObstructed(los.from, los.to, obstacles);
    if (losBlocked) p = removeOneDie(p);
    setObstructed(losBlocked);
    // CF: +1 die van een kleur die al in de pool zit én op deze range mag
    let added = null;
    if (useCF) {
      const prefs = cfColor ? [cfColor, ...DICE_ORDER] : DICE_ORDER;
      added = prefs.find((c) => p[c] > 0 && dieAllowed(c, r)) || null;
      if (added) p[added] += 1;
    }
    setCfAdded(added);
    setDice(rollPool(p));
    setRerollsLeft(0);
    setStep(3);
  };

  const tally = useMemoAR(() => {
    return dice.reduce((acc, d) => {
      const v = FACE_VALUE[d.face];
      acc.dmg += v.dmg; acc.crit += v.crit; acc.acc += v.acc;
      return acc;
    }, { dmg: 0, crit: 0, acc: 0 });
  }, [dice]);

  const reroll = (id) => { if (rerollsLeft <= 0) return; setDice((ds) => ds.map((d) => d.id === id ? { ...d, face: rollFace(d.color), rerolled: true } : d)); setRerollsLeft((n) => n - 1); };

  // ---- anti-squadron result (Scatter cancels all, Brace halves round up) ----
  const sqResult = useMemoAR(() => {
    if (!sqTarget) return null;
    let dmg = tally.dmg, scattered = false;
    if (defom.scatterId) { dmg = 0; scattered = true; }
    else if (defom.braceId) { dmg = Math.ceil(dmg / 2); }
    const hull = Math.max(0, sqTarget.hull - dmg);
    return { dmg, hull, destroyed: hull <= 0, scattered };
  }, [sqTarget, tally, defom]);

  const applySq = () => {
    if (!sqTarget || !sqResult || !onApplySquadron) return;
    const spentTokenIds = [defom.braceId, defom.scatterId].filter(Boolean);
    onApplySquadron(fa.id, sqTarget.id, sqResult.dmg, spentTokenIds);
    setApplied({ mode: "squadron", name: sqTarget.name, dmg: sqResult.dmg, destroyed: sqResult.destroyed, scattered: sqResult.scattered, preHull: sqTarget.hull, hull: sqResult.hull });
    setStep(6);
  };

  // accuracy can lock defender tokens (one accuracy per token)
  const availAccuracy = tally.acc - Object.keys(spentAccuracy).length;
  const toggleLock = (tokenId) => setSpentAccuracy((s) => {
    const n = { ...s }; if (n[tokenId]) delete n[tokenId]; else if (availAccuracy > 0) n[tokenId] = true; return n;
  });

  // alleen ready tokens zijn bruikbaar (voor verdediging én als accuracy-doel); exhausted alleen ter info
  const defenderReadyTokens = target ? target.defenseTokens.filter((dt) => dt.state === "ready") : [];
  const defenderShownTokens = target ? target.defenseTokens.filter((dt) => dt.state !== "discarded") : [];
  const isLocked = (id) => !!spentAccuracy[id];

  // ---- Intel Officer / NK-7 Ion Cannons (aanvaller-kant, na het rollen) ----
  const intelAvailable = mode === "ship" && !!target && upgradeIntelOfficerReady(fa);
  const nk7Available = mode === "ship" && !!target && upgradeNK7Ready(fa) && hasBlueCrit(dice);
  const intelId = intelAvailable ? intelTokenId : null;
  const nk7Id = nk7Available ? nk7TokenId : null;

  // ---- compute final damage given defensive choices ----
  const hitArc = hitZone || "front";
  const result = useMemoAR(() => {
    if (!target) return null;
    // Evade annuleert hele dice: ook crit/acc van de geannuleerde die vervalt
    const active = dice.filter((d) => !defom.evadeDieIds.includes(d.id));
    const t2 = active.reduce((acc, d) => {
      const v = FACE_VALUE[d.face];
      acc.dmg += v.dmg; acc.crit += v.crit; acc.acc += v.acc;
      return acc;
    }, { dmg: 0, crit: 0, acc: 0 });
    let dmg = t2.dmg, crit = t2.crit;
    if (defom.scatterId) return { ...resolveDamage(target, hitArc, 0, null), dmg: 0, crit: 0, scattered: true, tookCrit: false };
    if (defom.braceId) dmg = Math.ceil(dmg / 2);
    const redirect = defom.redirectArc ? { arc: defom.redirectArc, amount: 99 } : null;
    const res = resolveDamage(target, hitArc, dmg, redirect);
    // targeter_disruption (schadekaart): deze aanvaller mag geen critical-effecten resolven → geen kaart trekken
    // NK-7 is zélf een critical effect: één per aanval, dus dan geen schadekaart
    const tookCrit = !defom.containId && !noCritResolve && resolvesStandardCrit(crit, !!nk7Id);
    return { ...res, dmg, crit, tookCrit };
  }, [target, dice, defom, hitArc, noCritResolve, nk7Id]);

  const apply = () => {
    if (!target || !result) return;
    const snap = { preShield: target.shields[hitArc], preHull: target.hull };
    const spentTokenIds = [defom.braceId, defom.scatterId, defom.containId, defom.redirectTokenId, ...defom.evadeTokenIds].filter(Boolean);
    // NK-7: het gekozen token gaat sowieso weg. Intel Officer: alleen als het token
    // deze aanval daadwerkelijk is uitgegeven.
    const discardTokenIds = [nk7Id, intelId && spentTokenIds.includes(intelId) ? intelId : null].filter(Boolean);
    const usedUpgradeIds = [nk7Id ? "nk-7-ion-cannons" : null, intelId ? "intel-officer" : null].filter(Boolean);
    onApply({
      attackerId: fa.id, targetId: target.id, arc, hitArc, range,
      shields: result.shields, hull: result.hull, destroyed: result.destroyed,
      tookCrit: result.tookCrit, usedCF: useCF,
      lockedTokenIds: Object.keys(spentAccuracy),
      spentTokenIds, discardTokenIds, usedUpgradeIds,
    });
    if (nk7Id) addLog(`${fa.name}: NK-7 Ion Cannons — ${target.name} gooit een defense token af (geen schadekaart deze aanval).`, "atk");
    if (discardTokenIds.includes(intelId)) addLog(`${fa.name}: Intel Officer — het gemerkte token van ${target.name} gaat weg i.p.v. exhausted.`, "atk");
    addLog(`${fa.name} → ${target.name} (${arc}/${RANGE_LABEL[range]}): ${result.dmg} schade${result.tookCrit ? " + crit" : ""}${result.destroyed ? " — VERNIETIGD" : ""}`, result.destroyed ? "bad" : "atk");
    setApplied({ mode: "ship", ...result, ...snap });
    setStep(6);
  };

  const fc = FACTIONS[fa.faction].color;

  return (
    <div className="scrim" onPointerDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="dialog ar-dialog" style={{ "--fc": fc }}>
        <div className="dialog-head">
          <span className="sec-accent" style={{ background: fc }} />
          <span className="dh-title">Aanval — {fa.name}</span>
          <span style={{ flex: 1 }} />
          <div className="ar-steps">
            {["Doel", "Rol & modify", "Verdedig", "Schade"].map((s, i) => (
              <span key={i} className="ar-step" data-state={AR_STEP_IX[step] === i ? "active" : AR_STEP_IX[step] > i ? "done" : "todo"}>{s}</span>
            ))}
          </div>
          <button className="icon-btn" onClick={onClose} style={{ marginLeft: 10 }}>
            <svg width="14" height="14" viewBox="0 0 14 14"><path d="M2 2 L12 12 M12 2 L2 12" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></svg>
          </button>
        </div>

        <div className="dialog-body">
          {/* STEP 1-2: pick arc + target (or, for anti-squadron armament, a squadron target) */}
          {step === 1 ? (
            <>
              {hasAntiSquadron ? (
                <div className="field">
                  <span className="lbl">Aanvalstype</span>
                  <div className="range-seg">
                    <button data-active={mode === "ship"} onClick={() => { setMode("ship"); setTargetId(null); }}>Scheepsgeschut</button>
                    <button data-active={mode === "squadron"} onClick={() => { setMode("squadron"); setTargetId(null); }}>Anti-squadron</button>
                  </div>
                </div>
              ) : null}
              {mode === "ship" ? (
                <>
                  <div className="field">
                    <span className="lbl">Vuur-arc</span>
                    <div className="ar-arc-row">
                      {ARCS.map((a) => {
                        const n = poolSize(fa.attackDice[a]); const fired = fa.arcsFired.includes(a);
                        return (
                          <button key={a} className="ar-arc" data-active={arc === a} disabled={n === 0 || fired}
                                  onClick={() => setArc(a)}>
                            <ArcSwatch arc={a} /> {ARC_LABEL[a]}
                            <span className="ar-arc-n mono">{n}d{fired ? " ✓" : ""}</span>
                          </button>
                        );
                      })}
                    </div>
                  </div>
                  {useCF ? (
                    <div className="field">
                      <span className="lbl">Concentrate Fire — extra die</span>
                      <div className="range-seg">
                        {[[null, "Auto"], ["red", "Rood"], ["blue", "Blauw"], ["black", "Zwart"]].map(([c, lab]) => (
                          <button key={lab} data-active={cfColor === c} onClick={() => setCfColor(c)}>{lab}</button>
                        ))}
                      </div>
                      <span className="mono" style={{ fontSize: 10.5, color: "var(--text-faint)" }}>Alleen kleuren die al in de arc-pool zitten en op deze range mogen.</span>
                    </div>
                  ) : null}
                  <div className="field" style={{ marginBottom: 0 }}>
                    <span className="lbl">Doelwit</span>
                    <div className="ar-targets">
                      {candidates && candidates.length ? candidates.map(({ ship, range, inArc, seenFrom, freeZones, hitArc, zoneUsed }) => {
                        const okToFire = inArc && range && !zoneUsed;
                        const arcHint = inArc
                          ? `${freeZones.length} hull zone${freeZones.length === 1 ? "" : "s"} in arc`
                          : (seenFrom.length ? `alleen uit ${seenFrom.map((a) => ARC_LABEL[a]).join("/")}` : "in geen enkele arc");
                        return (
                        <button key={ship.id} className="ar-target" data-ok={okToFire} disabled={!range || zoneUsed}
                                onClick={() => okToFire && pickTarget(ship, freeZones)}>
                          <FactionDot faction={ship.faction} />
                          <div style={{ flex: 1, minWidth: 0 }}>
                            <div className="ar-tname">{ship.name}</div>
                            <div className="ar-tsub mono">{range ? RANGE_LABEL[range] : "buiten bereik"} · {zoneUsed ? `${ARC_LABEL[hitArc]}-zone al geraakt` : arcHint}</div>
                          </div>
                          {okToFire ? <span className="ar-go">Vuur →</span> : <span className="ar-no">{zoneUsed ? "zone al geraakt" : (range ? "andere arc" : "te ver")}</span>}
                        </button>
                        );
                      }) : <div className="mono ar-empty">Geen vijandige schepen binnen bereik in deze arc.</div>}
                    </div>
                  </div>
                </>
              ) : (
                <div className="field" style={{ marginBottom: 0 }}>
                  <span className="lbl">Squadron-doelwit (close range)</span>
                  <div className="ar-targets">
                    {sqCandidates.length ? sqCandidates.map((sq) => (
                      <button key={sq.id} className="ar-target" data-ok={true} onClick={() => beginSqRoll(sq)}>
                        <FactionDot faction={sq.faction} />
                        <div style={{ flex: 1, minWidth: 0 }}>
                          <div className="ar-tname">{sq.name}</div>
                          <div className="ar-tsub mono">close range · hull {sq.hull}</div>
                        </div>
                        <span className="ar-go">Vuur →</span>
                      </button>
                    )) : <div className="mono ar-empty">Geen vijandige squadrons binnen close range.</div>}
                  </div>
                </div>
              )}
            </>
          ) : null}

          {/* STEP 2: verdedigende hull zone kiezen (alleen als er meer dan één legaal is) */}
          {step === 2 && mode === "ship" && target ? (
            <>
              <div className="ar-context mono">{fa.name} · {ARC_LABEL[arc]} → {target.name} · {RANGE_LABEL[range]}</div>
              <div className="field" style={{ marginBottom: 0 }}>
                <span className="lbl">Verdedigende hull zone — welke rand ligt in je vuurboog?</span>
                <div className="ar-arc-row">
                  {zoneChoices.map((z) => (
                    <button key={z} className="ar-arc" onClick={() => beginRoll(target, z)}>
                      <ArcSwatch arc={z} /> {ARC_LABEL[z]}
                      <span className="ar-arc-n mono">{target.shields[z]} sh</span>
                    </button>
                  ))}
                </div>
                <span className="mono" style={{ fontSize: 10.5, color: "var(--text-faint)" }}>
                  Alleen zones waarvan een deel van de rand in de {ARC_LABEL[arc]}-arc valt en die naar je toe wijzen.
                </span>
              </div>
              <div className="dialog-inline-foot">
                <button className="btn btn-ghost" onClick={() => { setTargetId(null); setHitZone(null); setStep(1); }}>← Ander doel</button>
              </div>
            </>
          ) : null}

          {/* STEP 3 (anti-squadron): roll */}
          {step === 3 && mode === "squadron" && sqTarget ? (
            <>
              <div className="ar-context mono">{fa.name} (anti-squadron) → {sqTarget.name} · close range{obstructed ? " · geobstrueerd — 1 die verwijderd" : ""}</div>
              <div className="ar-tray">
                {dice.map((d) => (
                  <div key={d.id} className={"die" + (d.face === "acc" ? " is-acc" : "")} data-color={d.color}>
                    <div className="die-face"><FaceGlyph face={d.face} size={26} /></div>
                  </div>
                ))}
              </div>
              <div className="ar-tallies">
                <span><b className="mono">{tally.dmg}</b> schade</span>
                <span><b className="mono" style={{ color: "var(--bad)" }}>{tally.crit}</b> crit</span>
                <span><b className="mono" style={{ color: "var(--republic)" }}>{tally.acc}</b> accuracy</span>
              </div>
              <div className="dialog-inline-foot">
                <button className="btn btn-ghost" onClick={() => { setTargetId(null); setHitZone(null); setStep(1); setObstructed(false); }}>← Ander doel</button>
                <button className="btn btn-accent" onClick={() => setStep(4)}>Naar verdediging →</button>
              </div>
            </>
          ) : null}

          {/* STEP 4 (anti-squadron): defense tokens (Scatter/Brace) + apply */}
          {step === 4 && mode === "squadron" && sqTarget ? (
            <>
              <div className="ar-context mono">{sqTarget.name} verdedigt — {tally.dmg} inkomende schade</div>
              <div className="ar-def">
                {sqDefTokens.length === 0 ? <div className="mono ar-empty">Geen defense tokens beschikbaar.</div> : null}
                {sqDefTokens.map((dt) => {
                  const T = dt.type;
                  const on = defom.braceId === dt.id || defom.scatterId === dt.id;
                  return (
                    <button key={dt.id} className="ar-deftok" data-on={!!on}
                            onClick={() => setDefom((d) => {
                              if (T === "Brace") return { ...d, braceId: d.braceId === dt.id ? null : dt.id };
                              if (T === "Scatter") return { ...d, scatterId: d.scatterId === dt.id ? null : dt.id };
                              return d;
                            })}>
                      <span className="dtok-ico"><DefTokenIcon type={T} /></span>
                      <span className="ar-deftok-name">{T}</span>
                      <span className="ar-deftok-eff mono">{TOKEN_EFFECT[T]}</span>
                    </button>
                  );
                })}
              </div>
              <div className="ar-preview">
                <span className="lbl">Resultaat</span>
                <div className="ar-preview-grid">
                  <div><span className="mono ar-big">{sqResult ? sqResult.dmg : 0}</span><span className="ar-cap">schade na mods</span></div>
                  <div><span className="mono ar-big">{sqResult ? sqResult.hull : sqTarget.hull}</span><span className="ar-cap">hull na aanval</span></div>
                </div>
              </div>
              <div className="dialog-inline-foot">
                <button className="btn btn-ghost" onClick={() => setStep(3)}>← Dice</button>
                <button className="btn btn-accent" onClick={applySq}>Pas schade toe →</button>
              </div>
            </>
          ) : null}

          {/* STEP 3: roll + modify */}
          {step === 3 && mode === "ship" && target ? (
            <>
              <div className="ar-context mono">{fa.name} · {ARC_LABEL[arc]} → {target.name} · {RANGE_LABEL[range]} · raakt {ARC_LABEL[hitArc]}-zone{cfAdded ? ` · CF: +1 ${DICE[cfAdded].label.toLowerCase()}` : ""}{obstructed ? " · geobstrueerd — 1 die verwijderd" : ""}</div>
              <div className="ar-tray">
                {dice.map((d) => (
                  <div key={d.id} className={"die" + (d.face === "acc" ? " is-acc" : "")} data-color={d.color} onClick={() => reroll(d.id)} title={rerollsLeft > 0 ? "herrol" : ""}>
                    <div className="die-face"><FaceGlyph face={d.face} size={26} />{d.rerolled ? <span className="die-reroll-dot">r</span> : null}</div>
                  </div>
                ))}
              </div>
              <div className="ar-tallies">
                <span><b className="mono">{tally.dmg}</b> schade</span>
                <span><b className="mono" style={{ color: "var(--bad)" }}>{tally.crit}</b> crit</span>
                <span><b className="mono" style={{ color: "var(--republic)" }}>{tally.acc}</b> accuracy</span>
              </div>
              <div className="ar-modrow">
                <button className="btn btn-ghost" onClick={() => { setRerollsLeft(1 + upgradeBonusRerolls(fa)); }} disabled={rerollsLeft > 0}
                        title="Handmatige optie — niet aan een dial gekoppeld">Huisregel/upgrade: +{1 + upgradeBonusRerolls(fa)} herrol{1 + upgradeBonusRerolls(fa) > 1 ? "len" : ""}{rerollsLeft > 0 ? ` (${rerollsLeft} over)` : ""}</button>
              </div>
              {!noAccuracy && availAccuracy > 0 && defenderReadyTokens.length ? (
                <div className="ar-accuracy">
                  <span className="lbl">Accuracy — lock defense tokens ({availAccuracy} over)</span>
                  <div className="dtok-row">
                    {defenderReadyTokens.map((dt) => (
                      <button key={dt.id} className="dtok" data-state={isLocked(dt.id) ? "discarded" : "ready"} onClick={() => toggleLock(dt.id)}>
                        <span className="dtok-ico"><DefTokenIcon type={dt.type} /></span>{dt.type}{isLocked(dt.id) ? " 🔒" : ""}
                      </button>
                    ))}
                  </div>
                </div>
              ) : null}
              {nk7Available && defenderReadyTokens.length ? (
                <div className="ar-accuracy">
                  <span className="lbl">NK-7 Ion Cannons — blauwe crit: kaart uitputten, verdediger gooit 1 token af</span>
                  <div className="dtok-row">
                    {defenderReadyTokens.map((dt) => (
                      <button key={dt.id} className="dtok" data-state={nk7Id === dt.id ? "discarded" : "ready"}
                              onClick={() => setNk7TokenId((v) => (v === dt.id ? null : dt.id))}>
                        <span className="dtok-ico"><DefTokenIcon type={dt.type} /></span>{dt.type}{nk7Id === dt.id ? " ✕" : ""}
                      </button>
                    ))}
                  </div>
                  <span className="mono" style={{ fontSize: 10.5, color: "var(--text-faint)" }}>De verdediger kiest zelf welk token weggaat. Eén critical effect per aanval — hij trekt dan géén schadekaart.</span>
                </div>
              ) : null}
              {intelAvailable && defenderReadyTokens.length ? (
                <div className="ar-accuracy">
                  <span className="lbl">Intel Officer — merk 1 token: gebruikt de verdediger het deze aanval, dan gaat het weg</span>
                  <div className="dtok-row">
                    {defenderReadyTokens.filter((dt) => dt.id !== nk7Id).map((dt) => (
                      <button key={dt.id} className="dtok" data-state={intelId === dt.id ? "exhausted" : "ready"}
                              onClick={() => setIntelTokenId((v) => (v === dt.id ? null : dt.id))}>
                        <span className="dtok-ico"><DefTokenIcon type={dt.type} /></span>{dt.type}{intelId === dt.id ? " ◉" : ""}
                      </button>
                    ))}
                  </div>
                </div>
              ) : null}
              <div className="dialog-inline-foot">
                <button className="btn btn-ghost" onClick={() => { setTargetId(null); setHitZone(null); setIntelTokenId(null); setNk7TokenId(null); setStep(1); setObstructed(false); }}>← Ander doel</button>
                <button className="btn btn-accent" onClick={() => setStep(4)}>Naar verdediging →</button>
              </div>
            </>
          ) : null}

          {/* STEP 4: defense tokens */}
          {step === 4 && mode === "ship" && target ? (
            <>
              <div className="ar-context mono">{target.name} verdedigt — {tally.dmg} inkomende schade op {ARC_LABEL[hitArc]} ({target.shields[hitArc]} shields)</div>
              <div className="ar-def">
                {defenderShownTokens.length === 0 ? <div className="mono ar-empty">Geen defense tokens beschikbaar.</div> : null}
                {defenderShownTokens.map((dt) => {
                  const locked = isLocked(dt.id);
                  const exhausted = dt.state !== "ready";
                  const T = dt.type;
                  const nk7Gone = dt.id === nk7Id;                       // NK-7 heeft dit token al afgegooid
                  const evadeDead = T === "Evade" && !evadeUsable(range); // close range: Evade doet niets tegen een schip
                  const unusable = locked || exhausted || nk7Gone || evadeDead;
                  const on =
                    defom.braceId === dt.id || defom.scatterId === dt.id || defom.containId === dt.id ||
                    defom.redirectTokenId === dt.id || defom.evadeTokenIds.includes(dt.id);
                  return (
                    <button key={dt.id} className="ar-deftok" data-on={!!on} disabled={unusable}
                            onClick={() => {
                              if (unusable) return;
                              setDefom((d) => {
                                if (T === "Brace") return { ...d, braceId: d.braceId === dt.id ? null : dt.id };
                                if (T === "Scatter") return { ...d, scatterId: d.scatterId === dt.id ? null : dt.id };
                                if (T === "Contain") return { ...d, containId: d.containId === dt.id ? null : dt.id };
                                if (T === "Evade") return d.evadeTokenIds.includes(dt.id)
                                  ? { ...d, evadeTokenIds: d.evadeTokenIds.filter((x) => x !== dt.id), evadeDieIds: d.evadeDieIds.slice(0, d.evadeTokenIds.length - 1) }
                                  : { ...d, evadeTokenIds: [...d.evadeTokenIds, dt.id] };
                                if (T === "Redirect") return d.redirectTokenId === dt.id
                                  ? { ...d, redirectTokenId: null, redirectArc: null }
                                  : { ...d, redirectTokenId: dt.id, redirectArc: ARC_ADJACENT[hitArc][0] };
                                return d;
                              });
                            }}>
                      <span className="dtok-ico"><DefTokenIcon type={T} /></span>
                      <span className="ar-deftok-name">{T}{
                        locked ? " 🔒 gelockt"
                        : nk7Gone ? " ✕ afgegooid (NK-7)"
                        : exhausted ? " · exhausted"
                        : evadeDead ? " · geen effect op close range"
                        : intelId === dt.id ? " ◉ gemerkt (Intel Officer)"
                        : ""}</span>
                      <span className="ar-deftok-eff mono">{TOKEN_EFFECT[T]}</span>
                    </button>
                  );
                })}
                {defom.evadeTokenIds.length ? (() => {
                  // RRG 1.5: long = annuleer een die, medium = herrol een die,
                  // close = geen effect (het token is daar niet eens selecteerbaar).
                  const evadeMode = evadeModeForRange(range);
                  const rerolledCount = dice.filter((d) => d.evadeRerolled).length;
                  const maxUse = defom.evadeTokenIds.length;
                  return (
                  <div className="ar-redirect" style={{ flexWrap: "wrap" }}>
                    <span className="lbl">Evade — tik {maxUse === 1 ? "de die" : `max ${maxUse} dice`} om te {evadeMode === "cancel" ? "annuleren" : "herrollen"} ({RANGE_LABEL[range]})</span>
                    <div className="ar-tray" style={{ width: "100%" }}>
                      {dice.map((d) => {
                        const cancelled = defom.evadeDieIds.includes(d.id);
                        const rerolled = !!d.evadeRerolled;
                        const mark = cancelled || rerolled;
                        return (
                          <div key={d.id} className={"die" + (cancelled ? " is-cancelled" : "")} data-color={d.color}
                               style={mark ? { opacity: cancelled ? 0.35 : 1, outline: `2px solid ${cancelled ? "var(--bad)" : "var(--accent)"}` } : null}
                               title={rerolled ? "herrold" : undefined}
                               onClick={() => {
                                 if (evadeMode === "cancel") {
                                   setDefom((dd) => cancelled
                                     ? { ...dd, evadeDieIds: dd.evadeDieIds.filter((x) => x !== d.id) }
                                     : (dd.evadeDieIds.length < maxUse ? { ...dd, evadeDieIds: [...dd.evadeDieIds, d.id] } : dd));
                                 } else {
                                   if (rerolled) return; // herrol is definitief
                                   setDice((ds) => ds.filter((x) => x.evadeRerolled).length >= maxUse
                                     ? ds
                                     : ds.map((x) => x.id === d.id ? { ...x, face: rollFace(x.color), evadeRerolled: true } : x));
                                 }
                               }}>
                            <div className="die-face"><FaceGlyph face={d.face} size={26} /></div>
                          </div>
                        );
                      })}
                    </div>
                    {evadeMode === "reroll" ? <span className="mono" style={{ fontSize: 10.5, color: "var(--text-faint)", width: "100%" }}>{rerolledCount}/{maxUse} herrold — een herrol is definitief.</span> : null}
                  </div>
                  );
                })() : null}
                {defom.redirectArc ? (
                  <div className="ar-redirect">
                    <span className="lbl">Redirect naar</span>
                    {ARC_ADJACENT[hitArc].map((a) => (
                      <button key={a} className="btn btn-sq" data-active={defom.redirectArc === a} style={{ width: "auto", padding: "0 12px" }}
                              onClick={() => setDefom((d) => ({ ...d, redirectArc: a }))}>{ARC_LABEL[a]} ({target.shields[a]})</button>
                    ))}
                  </div>
                ) : null}
              </div>
              <div className="ar-preview">
                <span className="lbl">Resultaat</span>
                <div className="ar-preview-grid">
                  <div><span className="mono ar-big">{result.dmg}</span><span className="ar-cap">schade na mods</span></div>
                  <div><span className="mono ar-big" style={{ color: "var(--bad)" }}>{result.tookCrit ? "✦" : "—"}</span><span className="ar-cap">crit</span></div>
                  <div><span className="mono ar-big">{result.hullLoss}</span><span className="ar-cap">hull verlies</span></div>
                </div>
              </div>
              <div className="dialog-inline-foot">
                <button className="btn btn-ghost" onClick={() => setStep(3)}>← Dice</button>
                <button className="btn btn-accent" onClick={apply}>Pas schade toe →</button>
              </div>
            </>
          ) : null}

          {/* STEP 6: applied summary */}
          {step === 6 && applied && applied.mode === "squadron" ? (
            <div className="ar-done">
              <div className="ar-done-ico" data-bad={applied.destroyed}>{applied.destroyed ? "✕" : "✓"}</div>
              <div className="ar-done-title">{applied.destroyed ? `${applied.name} vernietigd` : `${applied.dmg} schade aan ${applied.name}`}</div>
              <div className="mono ar-done-sub">
                {applied.scattered ? "Scatter — alle schade geannuleerd." :
                 `${applied.dmg} schade · hull ${applied.preHull}→${applied.hull}`}
              </div>
              <div className="dialog-inline-foot" style={{ justifyContent: "center", marginTop: 18 }}>
                {fa.attacksMade < 2 && (arcsWithDice.length >= 1 || sqCandidates.length >= 1) ? (
                  <button className="btn" onClick={() => { setMode(hasAntiSquadron && sqCandidates.length ? "squadron" : "ship"); setArc(arcsWithDice[0] || arc); setStep(1); setTargetId(null); setHitZone(null); setDice([]); setSpentAccuracy({}); setDefom(EMPTY_DEFOM); setIntelTokenId(null); setNk7TokenId(null); setCfAdded(null); setApplied(null); setObstructed(false); }}>Tweede aanval</button>
                ) : null}
                <button className="btn btn-accent" onClick={onClose}>Klaar</button>
              </div>
            </div>
          ) : null}
          {step === 6 && applied && applied.mode !== "squadron" ? (
            <div className="ar-done">
              <div className="ar-done-ico" data-bad={applied.destroyed}>{applied.destroyed ? "✕" : "✓"}</div>
              <div className="ar-done-title">{applied.destroyed ? `${target ? target.name : "doel"} vernietigd` : `${applied.hullLoss} hull schade`}</div>
              <div className="mono ar-done-sub">
                {applied.scattered ? "Scatter — alle schade geannuleerd." :
                 `${applied.dmg} schade · shields ${ARC_LABEL[hitArc]} ${applied.preShield}→${applied.shields[hitArc]} · hull ${applied.preHull}→${applied.hull}${applied.tookCrit ? " · crit gemarkeerd" : ""}`}
              </div>
              <div className="dialog-inline-foot" style={{ justifyContent: "center", marginTop: 18 }}>
                {fa.attacksMade < 2 && (arcsWithDice.length >= 1 || sqCandidates.length >= 1) ? (
                  <button className="btn" onClick={() => { setMode("ship"); setArc(arcsWithDice[0] || arc); setStep(1); setTargetId(null); setHitZone(null); setDice([]); setSpentAccuracy({}); setDefom(EMPTY_DEFOM); setIntelTokenId(null); setNk7TokenId(null); setCfAdded(null); setApplied(null); setObstructed(false); }}>Tweede aanval (andere arc)</button>
                ) : null}
                <button className="btn btn-accent" onClick={onClose}>Klaar</button>
              </div>
            </div>
          ) : null}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { AttackResolver });
