import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import axios from 'axios';

// ─── Difficulty config ────────────────────────────────────────────────────────
const DIFFICULTIES = [
  { level: 1, name: 'Recruit',   desc: 'Nerfed enemies, +10 to checks, fast combat', badge: 'bg-emerald-800/60 border-emerald-600 text-emerald-200' },
  { level: 2, name: 'Standard',  desc: 'Default campaign balance',                   badge: 'bg-blue-800/60 border-blue-600 text-blue-200' },
  { level: 3, name: 'Hard',      desc: '×1.25 wounds, BS+5, −10 to checks',          badge: 'bg-yellow-800/60 border-yellow-500 text-yellow-200' },
  { level: 4, name: 'Brutal',    desc: '×1.5 wounds, BS+10, −20 checks, extra enemy, fear escalates', badge: 'bg-orange-800/60 border-orange-500 text-orange-200' },
  { level: 5, name: 'Legendary', desc: '×2 wounds, BS+15, −30 checks, 2 extra fear, no recovery',    badge: 'bg-red-900/60 border-red-600 text-red-200' },
];

const COMBAT_PROFILES = [
  { id: 'auto', name: 'Auto', desc: 'Sim chooses by WS/BS and gear.' },
  { id: 'balanced', name: 'Balanced', desc: 'Prefers ranged, but uses melee when clearly better.' },
  { id: 'ranged', name: 'Ranged', desc: 'Prioritises bolters, pistols, fire modes, and grenades.' },
  { id: 'melee', name: 'Melee', desc: 'Prioritises melee weapons, with fewer grenade throws.' },
];

function difficultyBadge(level) {
  const d = DIFFICULTIES.find(d => d.level === level) || DIFFICULTIES[1];
  return (
    <span className={`px-2 py-0.5 rounded text-xs font-bold border ${d.badge}`}>{d.name.toUpperCase()}</span>
  );
}

// ─── Helpers ──────────────────────────────────────────────────────────────────
function gradeColor(grade) {
  if (grade === 'A') return 'text-green-300';
  if (grade === 'B') return 'text-yellow-300';
  if (grade === 'C') return 'text-orange-300';
  return 'text-red-400';
}

function resultBadge(result) {
  if (result === 'VICTORY')
    return <span className="px-2 py-0.5 rounded text-xs font-bold bg-green-700/60 text-green-200">VICTORY</span>;
  return <span className="px-2 py-0.5 rounded text-xs font-bold bg-red-800/60 text-red-200">PARTIAL FAILURE</span>;
}

function EventLine({ ev }) {
  return (
    <div className="text-slate-400">
      {ev.type === 'crit'        && <span className="text-yellow-300">⚡ {ev.player} — Righteous Fury on {ev.target} ({ev.dos} DoS)</span>}
      {ev.type === 'initiative'  && <span className="text-slate-300">Initiative round {ev.round}: {(ev.order || []).join(' > ')}</span>}
      {ev.type === 'kill'        && <span className="text-green-300">☠ {ev.player} slew {ev.target}</span>}
      {ev.type === 'down'        && <span className="text-red-400">💀 {ev.player} went down (round {ev.round})</span>}
      {ev.type === 'checkRoll'   && <span className={ev.success ? 'text-green-300' : 'text-red-300'}>{ev.player} — {ev.check} [{ev.stat}] {ev.roll}/{ev.tn} {ev.success ? `${ev.dos} DoS` : `${ev.dof} DoF`}</span>}
      {ev.type === 'heroCheck'   && <span className="text-cyan-300">★ {ev.player} — {ev.check} ({ev.dos} DoS)</span>}
      {ev.type === 'complication'&& <span className="text-orange-300">⚡ Complication ({ev.player}): {ev.text?.slice(0, 80)}</span>}
      {ev.type === 'wound'       && <span className="text-red-300">🩸 {ev.player} took {ev.net} wounds from complication</span>}
      {ev.type === 'ammo'        && <span className={ev.specialAmmo ? 'text-cyan-300' : 'text-slate-400'}>{ev.player} fired {ev.spent} {ev.ammoType || 'Standard'} round{ev.spent === 1 ? '' : 's'} from {ev.weapon} ({ev.fireMode}, {ev.remaining} left)</span>}
      {ev.type === 'enemyAttack' && <span className={ev.hit ? 'text-red-300' : 'text-slate-500'}>{ev.enemy} {ev.mode} vs {ev.target}{ev.distance != null ? ` (${ev.distance}m)` : ''}: {ev.roll}/{ev.tn} {ev.hit ? 'hit' : 'miss'}</span>}
      {ev.type === 'battlemapMove' && <span className={ev.side === 'enemy' ? 'text-rose-300' : 'text-sky-300'}>{ev.actor || ev.player} advanced{ev.target ? ` toward ${ev.target}` : ''} to {ev.x},{ev.y} ({ev.travelled}m)</span>}
      {ev.type === 'battlemapAttack' && <span className={ev.blocked ? 'text-amber-300' : 'text-violet-300'}>{ev.attacker} vs {ev.target}{ev.weapon ? ` (${ev.weapon})` : ''}: {(ev.reasons || []).join(', ')}</span>}
      {ev.type === 'battlemapObjective' && <span className={ev.success ? 'text-emerald-300' : 'text-amber-300'}>{ev.player} {ev.action === 'secure-core' ? 'secured the gene-core' : ev.action === 'extract-core' ? 'withdrew the gene-core' : `worked ${ev.objective}`}: {ev.roll}/{ev.tn} {ev.success ? `${ev.dos} DoS` : `${ev.dof} DoF`}</span>}
      {ev.type === 'reinforcement' && <span className="text-rose-300">↺ Reinforcement from {ev.spawn}: {ev.enemy}</span>}
      {ev.type === 'horde_damage' && <span className="text-fuchsia-300">☠ {ev.player} cut down {ev.creaturesKilled} from {ev.target}</span>}
      {ev.type === 'hazard' && <span className="text-teal-300">☣ {ev.player}: {ev.text}</span>}
    </div>
  );
}

function fillForZoneTone(tone) {
  if (tone === 'spawn') return '#7c2d12';
  if (tone === 'corridor') return '#1d4ed8';
  if (tone === 'objective') return '#991b1b';
  if (tone === 'hazard') return '#0f766e';
  if (tone === 'vault') return '#4c1d95';
  return '#334155';
}

function lightingOverlay(lighting) {
  if (lighting === 'dark') return { fill: 'url(#sim-darkness)', opacity: 0.5 };
  if (lighting === 'dim') return { fill: 'url(#sim-dimness)', opacity: 0.35 };
  if (lighting === 'glow') return { fill: 'url(#sim-glow)', opacity: 0.32 };
  return null;
}

function elevationStroke(elevation) {
  if (Number(elevation) > 0) return '#facc15';
  if (Number(elevation) < 0) return '#22d3ee';
  return 'rgba(226,232,240,0.5)';
}

function elevationBadge(elevation) {
  if (Number(elevation) > 0) return `▲${Number(elevation)}`;
  if (Number(elevation) < 0) return `▼${Math.abs(Number(elevation))}`;
  return '';
}

function coverFill(kind) {
  return kind === 'heavy' ? '#b45309' : '#cbd5e1';
}

function coverPatternId(kind) {
  return kind === 'heavy' ? 'sim-cover-heavy' : 'sim-cover-light';
}

function hazardPatternId(kind) {
  if (kind === 'vent') return 'sim-hazard-vent';
  if (kind === 'agility') return 'sim-hazard-agility';
  return 'sim-hazard-default';
}

function findActorKeyByName(name, positions, preferredSide = '') {
  const allKeys = Object.keys(positions || {});
  const preferred = preferredSide ? `${preferredSide}:${name}` : '';
  if (preferred && positions[preferred]) return preferred;
  return allKeys.find((key) => key.slice(key.indexOf(':') + 1) === name) || null;
}

function replayStateAtIndex(events, index, initialPositions = {}) {
  const positions = Object.entries(initialPositions || {}).reduce((acc, [key, pos]) => {
    if (pos && Number.isFinite(Number(pos.x)) && Number.isFinite(Number(pos.y))) {
      acc[key] = { x: Number(pos.x), y: Number(pos.y) };
    }
    return acc;
  }, {});
  const eliminated = {};
  const recentAttacks = [];

  for (let i = 0; i <= index && i < events.length; i += 1) {
    const ev = events[i];
    if (ev.type === 'battlemapMove') {
      const name = ev.actor || ev.player;
      if (!name) continue;
      const key = `${ev.side === 'enemy' ? 'enemy' : 'player'}:${name}`;
      positions[key] = { x: Number(ev.x), y: Number(ev.y) };
      continue;
    }
    if (ev.type === 'battlemapAttack') {
      if (ev.blocked) continue;
      const attackerKey = findActorKeyByName(ev.attacker, positions);
      recentAttacks.push({
        mode: ev.mode || 'ranged',
        attackerKey,
        targetKey: findActorKeyByName(ev.target, positions),
        attacker: ev.attacker,
        target: ev.target,
        attackerSide: attackerKey?.startsWith('enemy:') ? 'enemy' : 'player',
        glyph: ev.glyph || (ev.mode === 'melee' ? 'melee' : 'ranged'),
        step: i,
      });
      continue;
    }
    if (ev.type === 'enemyAttack') {
      recentAttacks.push({
        mode: ev.mode || 'ranged',
        attackerKey: findActorKeyByName(ev.enemy, positions, 'enemy'),
        targetKey: findActorKeyByName(ev.target, positions, 'player'),
        attacker: ev.enemy,
        target: ev.target,
        attackerSide: 'enemy',
        glyph: ev.glyph || (ev.mode === 'melee' ? 'melee' : 'ranged'),
        step: i,
      });
      continue;
    }
    if (ev.type === 'kill') {
      const key = findActorKeyByName(ev.target, positions, 'enemy');
      if (key && positions[key]) {
        eliminated[key] = { ...positions[key], name: ev.target, kind: 'kill' };
        delete positions[key];
      }
      continue;
    }
    if (ev.type === 'down') {
      const key = findActorKeyByName(ev.player, positions, 'player');
      if (key && positions[key]) {
        eliminated[key] = { ...positions[key], name: ev.player, kind: 'down' };
        delete positions[key];
      }
    }
  }

  const activeAttacks = recentAttacks
    .filter((attack) => index - attack.step <= 2)
    .map((attack) => ({
      ...attack,
      attackerPos: attack.attackerKey ? positions[attack.attackerKey] || eliminated?.[attack.attackerKey] : null,
      targetPos: attack.targetKey ? positions[attack.targetKey] || eliminated?.[attack.targetKey] : null,
      age: index - attack.step,
    }))
    .filter((attack) => attack.attackerPos && attack.targetPos);

  return { positions, eliminated, activeAttacks };
}

function ReplayMap({ battlemap, positions, eliminated, activeAttacks }) {
  const width = Number(battlemap?.gridWidth) || 40;
  const height = Number(battlemap?.gridHeight) || 30;
  const zones = Array.isArray(battlemap?.zones) ? battlemap.zones : [];
  const doors = Array.isArray(battlemap?.doors) ? battlemap.doors : [];
  const cover = Array.isArray(battlemap?.cover) ? battlemap.cover : [];
  const blockers = Array.isArray(battlemap?.blockers) ? battlemap.blockers : [];
  const hazards = Array.isArray(battlemap?.hazards) ? battlemap.hazards : [];
  const tokens = Object.entries(positions);
  const eliminatedTokens = Object.entries(eliminated || {});

  return (
    <svg
      viewBox={`0 0 ${width} ${height}`}
      className="w-full rounded border border-slate-700 bg-slate-950/70"
      style={{ maxHeight: 320 }}
      data-testid="simulation-replay-map"
      aria-label="Simulation battlemap replay"
    >
      <defs>
        <pattern id="sim-darkness" width="1.6" height="1.6" patternUnits="userSpaceOnUse">
          <rect width="1.6" height="1.6" fill="rgba(15,23,42,0.6)" />
          <circle cx="0.45" cy="0.45" r="0.12" fill="rgba(129,140,248,0.6)" />
          <circle cx="1.2" cy="1.1" r="0.12" fill="rgba(129,140,248,0.45)" />
        </pattern>
        <pattern id="sim-dimness" width="2" height="2" patternUnits="userSpaceOnUse">
          <rect width="2" height="2" fill="rgba(30,41,59,0.18)" />
          <line x1="0" y1="2" x2="2" y2="0" stroke="rgba(191,219,254,0.18)" strokeWidth="0.08" />
        </pattern>
        <radialGradient id="sim-glow" cx="50%" cy="50%" r="60%">
          <stop offset="0%" stopColor="rgba(196,181,253,0.48)" />
          <stop offset="55%" stopColor="rgba(168,85,247,0.22)" />
          <stop offset="100%" stopColor="rgba(30,41,59,0)" />
        </radialGradient>
        <pattern id="sim-cover-light" width="0.8" height="0.8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
          <line x1="0" y1="0" x2="0" y2="0.8" stroke="rgba(15,23,42,0.38)" strokeWidth="0.12" />
        </pattern>
        <pattern id="sim-cover-heavy" width="1.1" height="1.1" patternUnits="userSpaceOnUse">
          <line x1="0" y1="0" x2="1.1" y2="1.1" stroke="rgba(15,23,42,0.45)" strokeWidth="0.12" />
          <line x1="1.1" y1="0" x2="0" y2="1.1" stroke="rgba(15,23,42,0.45)" strokeWidth="0.12" />
        </pattern>
        <pattern id="sim-hazard-default" width="0.9" height="0.9" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
          <line x1="0" y1="0" x2="0" y2="0.9" stroke="rgba(240,253,250,0.55)" strokeWidth="0.08" />
        </pattern>
        <pattern id="sim-hazard-agility" width="1.2" height="1.2" patternUnits="userSpaceOnUse">
          <path d="M0.15 1 L0.6 0.2 L1.05 1 Z" fill="rgba(255,251,235,0.45)" />
        </pattern>
        <pattern id="sim-hazard-vent" width="1.2" height="1.2" patternUnits="userSpaceOnUse">
          <circle cx="0.3" cy="0.35" r="0.12" fill="rgba(240,253,250,0.5)" />
          <circle cx="0.7" cy="0.75" r="0.14" fill="rgba(240,253,250,0.36)" />
        </pattern>
      </defs>
      {Array.from({ length: width + 1 }, (_, index) => (
        <line
          key={`grid-v-${index}`}
          x1={index}
          y1="0"
          x2={index}
          y2={height}
          stroke="rgba(148,163,184,0.18)"
          strokeWidth="0.04"
        />
      ))}
      {Array.from({ length: height + 1 }, (_, index) => (
        <line
          key={`grid-h-${index}`}
          x1="0"
          y1={index}
          x2={width}
          y2={index}
          stroke="rgba(148,163,184,0.18)"
          strokeWidth="0.04"
        />
      ))}
      {zones.map(zone => (
        <g key={zone.id}>
          <rect x={zone.x} y={zone.y} width={zone.w} height={zone.h} fill={fillForZoneTone(zone.tone)} fillOpacity="0.35" stroke={elevationStroke(zone.elevation)} strokeWidth="0.15" />
          {lightingOverlay(zone.lighting) && (
            <rect x={zone.x} y={zone.y} width={zone.w} height={zone.h} fill={lightingOverlay(zone.lighting).fill} fillOpacity={lightingOverlay(zone.lighting).opacity} />
          )}
          {elevationBadge(zone.elevation) && (
            <text x={zone.x + zone.w - 0.4} y={zone.y + zone.h - 0.3} textAnchor="end" fontSize="0.75" fill={elevationStroke(zone.elevation)}>{elevationBadge(zone.elevation)}</text>
          )}
        </g>
      ))}
      {cover.map(item => (
        <g key={item.id}>
          <rect x={item.x} y={item.y} width={item.w} height={item.h} fill={coverFill(item.kind)} fillOpacity={item.kind === 'heavy' ? 0.86 : 0.64} />
          <rect x={item.x} y={item.y} width={item.w} height={item.h} fill={`url(#${coverPatternId(item.kind)})`} fillOpacity="0.9" />
        </g>
      ))}
      {doors.map((door) => (
        <g key={door.id}>
          <rect x={door.x} y={door.y} width={door.w} height={door.h} rx="0.08" fill="#f8fafc" fillOpacity="0.95" stroke="#0f172a" strokeWidth="0.08" />
        </g>
      ))}
      {(battlemap?.hazards || []).map(item => (
        <g key={item.id}>
          <rect x={item.x} y={item.y} width={item.w} height={item.h} fill={item.kind === 'agility' ? '#f59e0b' : '#0f766e'} fillOpacity="0.45" stroke="#f8fafc" strokeDasharray="0.25 0.25" strokeWidth="0.12" />
          <rect x={item.x} y={item.y} width={item.w} height={item.h} fill={`url(#${hazardPatternId(item.kind)})`} fillOpacity="0.9" />
        </g>
      ))}
      {blockers.map(item => (
        <rect key={item.id} x={item.x} y={item.y} width={item.w} height={item.h} fill="#1e293b" stroke="#475569" strokeWidth="0.12" />
      ))}
      {hazards.map(item => (
        <g key={item.id}>
          <rect x={item.x} y={item.y} width={item.w} height={item.h} fill={item.kind === 'agility' ? '#f59e0b' : '#0f766e'} fillOpacity="0.45" stroke="#f8fafc" strokeDasharray="0.25 0.25" strokeWidth="0.12" />
          <rect x={item.x} y={item.y} width={item.w} height={item.h} fill={`url(#${hazardPatternId(item.kind)})`} fillOpacity="0.9" />
        </g>
      ))}
      {(activeAttacks || []).map((attack, idx) => {
        const fade = attack.age === 0 ? 1 : attack.age === 1 ? 0.6 : 0.35;
        const isEnemy = attack.attackerSide === 'enemy';
        const meleeStroke = isEnemy ? '#fb7185' : '#38bdf8';
        const rangedStroke = isEnemy ? '#f472b6' : '#60a5fa';
        const rangedFill = isEnemy ? '#f9a8d4' : '#93c5fd';
        const midX = (attack.attackerPos.x + attack.targetPos.x) / 2;
        const midY = (attack.attackerPos.y + attack.targetPos.y) / 2;
        return (
          <g key={`${attack.attacker}-${attack.target}-${attack.step}-${idx}`} data-testid="simulation-attack-marker" opacity={fade}>
            {(attack.glyph || attack.mode) === 'explosive' ? (
              <>
                <line x1={attack.attackerPos.x} y1={attack.attackerPos.y} x2={attack.targetPos.x} y2={attack.targetPos.y} stroke={rangedStroke} strokeWidth="0.18" strokeDasharray="0.28 0.22" strokeLinecap="round" />
                <circle cx={midX} cy={midY} r="0.85" fill={rangedFill} stroke={rangedStroke} strokeWidth="0.12" />
                <text x={midX} y={midY + 0.18} textAnchor="middle" fontSize="0.95" fill="#0f172a" style={{ fontWeight: 700 }}>✹</text>
              </>
            ) : attack.mode === 'melee' ? (
              <>
                <line x1={attack.attackerPos.x} y1={attack.attackerPos.y} x2={attack.targetPos.x} y2={attack.targetPos.y} stroke={meleeStroke} strokeWidth="0.28" strokeLinecap="round" />
                <circle cx={attack.targetPos.x} cy={attack.targetPos.y} r="1.2" fill="none" stroke={meleeStroke} strokeWidth="0.18" />
                <text x={midX} y={midY + 0.15} textAnchor="middle" fontSize="0.9" fill={meleeStroke} style={{ fontWeight: 700 }}>⚔</text>
              </>
            ) : (
              <>
                <line x1={attack.attackerPos.x} y1={attack.attackerPos.y} x2={attack.targetPos.x} y2={attack.targetPos.y} stroke={rangedStroke} strokeWidth="0.22" strokeDasharray="0.5 0.35" strokeLinecap="round" />
                <polygon points={`${attack.targetPos.x},${attack.targetPos.y} ${attack.targetPos.x - 0.9},${attack.targetPos.y - 0.35} ${attack.targetPos.x - 0.9},${attack.targetPos.y + 0.35}`} fill={rangedFill} />
                <text x={midX} y={midY + 0.15} textAnchor="middle" fontSize="0.9" fill={rangedStroke} style={{ fontWeight: 700 }}>✦</text>
              </>
            )}
          </g>
        );
      })}
      {tokens.map(([key, pos]) => {
        const isEnemy = key.startsWith('enemy:');
        const name = key.slice(key.indexOf(':') + 1);
        return (
          <g key={key}>
            <circle cx={pos.x} cy={pos.y} r="0.9" fill={isEnemy ? '#ef4444' : '#38bdf8'} stroke="#0f172a" strokeWidth="0.15" />
            <text x={pos.x} y={pos.y - 1.2} textAnchor="middle" fontSize="1.1" fill="#e2e8f0">{name}</text>
          </g>
        );
      })}
      {eliminatedTokens.map(([key, pos]) => (
        <g key={key} data-testid={`simulation-death-marker-${key}`}>
          <line x1={pos.x - 0.8} y1={pos.y - 0.8} x2={pos.x + 0.8} y2={pos.y + 0.8} stroke="#f8fafc" strokeWidth="0.2" strokeLinecap="round" />
          <line x1={pos.x + 0.8} y1={pos.y - 0.8} x2={pos.x - 0.8} y2={pos.y + 0.8} stroke="#f8fafc" strokeWidth="0.2" strokeLinecap="round" />
        </g>
      ))}
    </svg>
  );
}

const REPLAY_SPEEDS = [
  { label: 'Slow', ms: 700 },
  { label: 'Normal', ms: 350 },
  { label: 'Fast', ms: 120 },
];

const REPLAY_VIEW_MODES = [
  { id: 'clean', label: 'Clean' },
  { id: 'tactical', label: 'Tactical' },
];

function SceneReplay({ scene }) {
  const events = useMemo(() => scene.events || [], [scene]);
  // startingPositions is the pre-round-1 snapshot; battlemap.positions is the
  // end-of-combat state (kept for older saved simulations run before this
  // field existed) — starting positions is what the replay must seed from,
  // or every token would render frozen at its final position until (if ever)
  // its own first move event corrects it.
  const initialPositions = useMemo(
    () => scene.battlemap?.startingPositions || scene.battlemap?.positions || {},
    [scene]
  );
  const [index, setIndex] = useState(-1);
  const [playing, setPlaying] = useState(false);
  const [speedMs, setSpeedMs] = useState(350);
  const [viewMode, setViewMode] = useState('tactical');
  const logRef = useRef(null);

  useEffect(() => {
    if (!playing) return undefined;
    if (index >= events.length - 1) { setPlaying(false); return undefined; }
    const timer = setTimeout(() => setIndex(i => Math.min(events.length - 1, i + 1)), speedMs);
    return () => clearTimeout(timer);
  }, [playing, index, events.length, speedMs]);

  useEffect(() => {
    if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
  }, [index]);

  const replayState = useMemo(() => replayStateAtIndex(events, index, initialPositions), [events, index, initialPositions]);
  const visibleEvents = events.slice(0, index + 1);
  const atEnd = events.length > 0 && index >= events.length - 1;

  const handlePlayClick = () => {
    if (playing) { setPlaying(false); return; }
    if (atEnd) setIndex(-1);
    setPlaying(true);
  };

  return (
    <div className="rounded-lg border border-slate-700/50 bg-slate-950/40 p-2 space-y-2">
      <div className="flex flex-wrap items-stretch gap-2">
        <div className="flex flex-wrap items-center gap-1 rounded-lg border border-slate-700/70 bg-slate-900/90 px-2 py-1.5">
          <span className="mr-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500">Playback</span>
          <button
            type="button"
            onClick={handlePlayClick}
            className="rounded bg-blue-700 hover:bg-blue-600 px-2.5 py-1.5 text-xs font-semibold text-white"
          >
            {playing ? '⏸ Pause' : atEnd ? '↻ Replay' : '▶ Play'}
          </button>
          <button
            type="button"
            onClick={() => { setPlaying(false); setIndex(-1); }}
            className="rounded bg-slate-700 hover:bg-slate-600 px-2.5 py-1.5 text-xs text-slate-200"
          >
            ⏮ Reset
          </button>
          <button
            type="button"
            onClick={() => setIndex(i => Math.min(events.length - 1, i + 1))}
            disabled={playing || index >= events.length - 1}
            className="rounded bg-slate-700 hover:bg-slate-600 px-2.5 py-1.5 text-xs text-slate-200 disabled:opacity-40"
          >
            Step ▶
          </button>
        </div>
        <label className="flex items-center gap-2 rounded-lg border border-slate-700/70 bg-slate-900/90 px-2 py-1.5 text-xs text-slate-300">
          <span className="text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500">Speed</span>
          <select
            value={speedMs}
            onChange={e => setSpeedMs(Number(e.target.value))}
            className="bg-slate-700 border border-slate-600 rounded px-2 py-1 text-xs text-slate-200"
          >
            {REPLAY_SPEEDS.map(s => <option key={s.ms} value={s.ms}>{s.label}</option>)}
          </select>
        </label>
        <div className="flex items-center gap-1 rounded-lg border border-cyan-700/60 bg-cyan-950/20 px-2 py-1.5">
          <span className="mr-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-cyan-300/75">View</span>
          {REPLAY_VIEW_MODES.map((mode) => (
            <button
              key={mode.id}
              type="button"
              onClick={() => setViewMode(mode.id)}
              className={`rounded px-2.5 py-1.5 text-xs font-semibold transition-colors ${
                viewMode === mode.id
                  ? 'bg-cyan-500 text-slate-950'
                  : 'bg-slate-900/80 text-slate-300 hover:bg-slate-800 hover:text-slate-100'
              }`}
            >
              {mode.label}
            </button>
          ))}
        </div>
        <div className="ml-auto flex min-w-[8rem] items-center justify-end rounded-lg border border-slate-700/70 bg-slate-900/90 px-2.5 py-1.5 text-right">
          <div>
            <div className="text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500">Progress</div>
            <div className="text-sm font-semibold text-slate-200">{Math.max(0, index + 1)}/{events.length}</div>
          </div>
        </div>
      </div>
      <div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-slate-800/80 bg-slate-900/60 px-3 py-2">
        <div>
          <div className="text-xs font-semibold text-slate-200">Scene Replay</div>
          <div className="text-[11px] text-slate-400">
            {viewMode === 'clean'
              ? 'Clean view focuses on token movement, attacks, and removals.'
              : 'Tactical view adds cover, elevation, lighting, hazards, and attack trails.'}
          </div>
        </div>
        <div className="rounded border border-slate-700/70 bg-slate-950/60 px-2 py-1 text-[11px] text-slate-400">
          {playing ? 'Playback running' : atEnd ? 'Replay finished' : index < 0 ? 'Ready to start' : 'Paused'}
        </div>
      </div>
      <div className="grid grid-cols-1 gap-2 xl:grid-cols-[minmax(0,1.2fr)_minmax(18rem,0.8fr)]">
        <div className="rounded-lg border border-slate-800/80 bg-slate-950/35 p-2">
          <div className="mb-2 flex items-center justify-between gap-2">
            <div className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">Battle Map</div>
            <div className="text-[11px] text-slate-500">{Object.keys(replayState.positions || {}).length} active tokens</div>
          </div>
          <ReplayMap
            battlemap={
              viewMode === 'clean'
                ? {
                  ...scene.battlemap,
                  cover: [],
                  blockers: [],
                  hazards: [],
                  zones: Array.isArray(scene.battlemap?.zones)
                    ? scene.battlemap.zones.map((zone) => ({
                      ...zone,
                      lighting: 'normal',
                      elevation: 0,
                    }))
                    : [],
                }
                : scene.battlemap
            }
            positions={replayState.positions}
            eliminated={replayState.eliminated}
            activeAttacks={viewMode === 'clean' ? [] : replayState.activeAttacks}
          />
        </div>
        <div className="rounded-lg border border-slate-800/80 bg-slate-950/35 p-2">
          <div className="mb-2 flex items-center justify-between gap-2">
            <div className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">Event Log</div>
            <div className="text-[11px] text-slate-500">{visibleEvents.length} shown</div>
          </div>
          <div ref={logRef} className="max-h-72 overflow-y-auto space-y-0.5 pr-1 text-xs">
            {visibleEvents.length === 0 && <div className="text-slate-500">Starting positions loaded. Press Play to replay this scene round by round.</div>}
            {visibleEvents.map((ev, i) => <EventLine key={i} ev={ev} />)}
          </div>
        </div>
      </div>
      {viewMode === 'tactical' && (
        <div className="flex flex-wrap gap-1 text-[10px] text-slate-400">
          <span className="rounded border border-yellow-500/50 bg-yellow-950/20 px-1.5 py-0.5 text-yellow-100">▲/▼ elevation</span>
          <span className="rounded border border-emerald-500/50 bg-emerald-950/20 px-1.5 py-0.5 text-emerald-100">white bars = doors</span>
          <span className="rounded border border-slate-500/50 bg-slate-800 px-1.5 py-0.5 text-slate-100">slash = light cover</span>
          <span className="rounded border border-amber-700/50 bg-amber-950/20 px-1.5 py-0.5 text-amber-100">crosshatch = heavy cover</span>
          <span className="rounded border border-indigo-500/50 bg-indigo-950/20 px-1.5 py-0.5 text-indigo-100">blue overlay = darkness</span>
          <span className="rounded border border-cyan-500/50 bg-cyan-950/20 px-1.5 py-0.5 text-cyan-100">dashed = hazard</span>
          <span className="rounded border border-sky-500/50 bg-sky-950/20 px-1.5 py-0.5 text-sky-100">blue attacks = players</span>
          <span className="rounded border border-pink-500/50 bg-pink-950/20 px-1.5 py-0.5 text-pink-100">pink attacks = enemies</span>
          <span className="rounded border border-violet-500/50 bg-violet-950/20 px-1.5 py-0.5 text-violet-100">✦ ranged · ⚔ melee · ✹ explosive</span>
          <span className="rounded border border-slate-500/50 bg-slate-900 px-1.5 py-0.5 text-slate-100">1 checker = 10m</span>
        </div>
      )}
    </div>
  );
}

function SceneRow({ scene, idx }) {
  const isCombat = scene.type === 'combat' || scene.cleared !== undefined;
  const hasBattlemap = Boolean(scene.battlemap?.active);
  const [open, setOpen] = useState(hasBattlemap);
  const [mode, setMode] = useState(hasBattlemap ? 'replay' : 'log'); // 'log' | 'replay'
  const passed   = isCombat ? scene.cleared : scene.outcome === 'success';
  const hasEvents = (scene.events || []).length > 0;

  return (
    <div className="border border-slate-700/40 rounded-lg mb-1 overflow-hidden">
      <button
        onClick={() => setOpen(o => !o)}
        className="w-full flex items-center justify-between px-3 py-2 bg-slate-800/60 hover:bg-slate-700/60 text-sm text-left"
      >
        <span className="flex items-center gap-2">
          <span className="text-slate-400 w-5 text-right">{idx + 1}.</span>
          <span className={passed ? 'text-green-300' : 'text-yellow-300'}>{passed ? '✅' : '⚠️'}</span>
          <span className="text-slate-100 font-medium">{scene.title}</span>
          <span className="text-slate-500 text-xs">[{scene.type}]</span>
        </span>
        <span className="text-slate-500 text-xs">{open ? '▲' : '▼'}</span>
      </button>
      {open && (
        <div className="px-4 py-2 bg-slate-900/40 text-xs text-slate-300 space-y-1">
          {isCombat && (
            <div>{scene.cleared
              ? <span className="text-green-300">All enemies eliminated.</span>
              : <span className="text-yellow-300">{scene.enemiesRemaining} enemies remained — squad withdrew.</span>}
            </div>
          )}
          {hasBattlemap && (
            <div className="rounded border border-slate-700/40 bg-slate-950/40 px-2 py-1 text-slate-300">
              Battlemap: {scene.battlemap.stationsOnline}/3 stations online, gene-core {scene.battlemap.geneCoreSecuredRounds}/3, extraction {scene.battlemap.extractionProgress || 0}/2, reinforcements {scene.battlemap.reinforcementWaves}, objective {scene.battlemap.objectivesMet ? 'met' : 'failed'}
            </div>
          )}
          {!isCombat && scene.total != null && (
            <div>{scene.successes}/{scene.total} checks passed — <span className={passed ? 'text-green-300' : 'text-yellow-300'}>{passed ? 'Success' : 'Partial'}</span></div>
          )}
          {hasEvents && (
            <div className="mt-1">
              <div className="flex gap-1 mb-1">
                <button
                  type="button"
                  onClick={() => setMode('log')}
                  className={`rounded px-2 py-0.5 text-[11px] font-semibold ${mode === 'log' ? 'bg-slate-700 text-slate-100' : 'text-slate-500 hover:text-slate-300'}`}
                >
                  Full Log
                </button>
                <button
                  type="button"
                  onClick={() => setMode('replay')}
                  className={`rounded px-2 py-0.5 text-[11px] font-semibold ${mode === 'replay' ? 'bg-slate-700 text-slate-100' : 'text-slate-500 hover:text-slate-300'}`}
                >
                  🎬 Live Replay
                </button>
              </div>
              {mode === 'log' && (
                <div className="space-y-0.5">
                  {scene.events.map((ev, i) => <EventLine key={i} ev={ev} />)}
                </div>
              )}
              {mode === 'replay' && <SceneReplay scene={scene} />}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

function PlayerCard({ card }) {
  const [open, setOpen] = useState(false);
  return (
    <div className="border border-slate-700/50 rounded-xl overflow-hidden bg-slate-800/40">
      <button onClick={() => setOpen(o => !o)} className="w-full text-left p-3 hover:bg-slate-700/30">
        <div className="flex items-center justify-between">
          <div>
            <span className="font-bold text-white text-sm">{card.name.toUpperCase()}</span>
            <span className="ml-2 text-xs text-slate-400">{card.chapter} {card.spec}</span>
          </div>
          <span className={`text-2xl font-black ${gradeColor(card.grade)}`}>{card.grade}</span>
        </div>
        <div className="flex gap-3 mt-1 text-xs">
          <span className={card.alive ? 'text-green-300' : 'text-red-400'}>
            {card.alive ? `ALIVE (${card.woundsRemaining}/${card.maxWounds}W)` : 'DOWN'}
          </span>
          <span className="text-slate-400">{card.successRate}% success</span>
          <span className="text-green-400">{card.successCount}✅</span>
          <span className="text-red-400">{card.failCount}❌</span>
          {card.combatProfile && <span className="text-cyan-300">Profile: {card.combatProfile}</span>}
          {card.fateSpends > 0 && <span className="text-purple-300">✧ Fate ×{card.fateSpends}</span>}
        </div>
        {card.roleFit && (
          <div className="mt-1 text-xs text-slate-400">
            Best fit: <span className="text-cyan-200">{card.roleFit.best}</span> · {card.roleFit.why}
          </div>
        )}
        {card.conditions?.length > 0 && (
          <div className="mt-1 text-xs text-yellow-300">Conditions: {card.conditions.join(', ')}</div>
        )}
        {card.ammo?.length > 0 && (
          <div className="mt-1 flex flex-wrap gap-1 text-[11px]">
            {card.ammo.map((ammo, i) => (
              <span key={`${ammo.name}-${i}`} className={ammo.specialAmmo ? 'rounded bg-cyan-950/50 px-1.5 py-0.5 text-cyan-200' : 'rounded bg-slate-900/60 px-1.5 py-0.5 text-slate-300'}>
                {ammo.name}: {ammo.remaining}/{ammo.clipSize} {ammo.ammoType || 'Standard'}
              </span>
            ))}
          </div>
        )}
      </button>
      {open && (
        <div className="border-t border-slate-700/40 px-3 py-2 text-xs space-y-2">
          {card.ammo?.length > 0 && (
            <div>
              <div className="text-cyan-300 font-semibold mb-0.5">Ammo</div>
              {card.ammo.map((ammo, i) => (
                <div key={`${ammo.name}-${i}`} className="text-slate-300 pl-2">
                  • {ammo.name}: {ammo.remaining}/{ammo.clipSize} {ammo.ammoType || 'Standard'} left, {ammo.shotsFired} fired
                </div>
              ))}
            </div>
          )}
          {card.heroMoments?.length > 0 && (
            <div>
              <div className="text-green-300 font-semibold mb-0.5">★ Hero Moments</div>
              {card.heroMoments.map((h, i) => <div key={i} className="text-slate-300 pl-2">• {h}</div>)}
            </div>
          )}
          {card.shameList?.length > 0 && (
            <div>
              <div className="text-red-400 font-semibold mb-0.5">✗ Shame List</div>
              {card.shameList.map((s, i) => <div key={i} className="text-slate-400 pl-2">• {s}</div>)}
            </div>
          )}
          {!card.heroMoments?.length && !card.shameList?.length && (
            <div className="text-slate-500">No notable moments recorded.</div>
          )}
        </div>
      )}
    </div>
  );
}

function RollFeedPanel({ feed, filterPlayer }) {
  const visible = filterPlayer ? feed.filter(r => r.player === filterPlayer) : feed;

  return (
    <div className="space-y-1 max-h-72 overflow-y-auto pr-1">
      {visible.length === 0 && <div className="text-slate-500 text-sm">No rolls recorded.</div>}
      {visible.map((r, i) => (
        <div key={i} className={`flex items-start gap-2 text-xs px-2 py-1 rounded ${r.success ? 'bg-green-900/20' : 'bg-red-900/20'}`}>
          <span className="mt-0.5 shrink-0">{r.success ? '✅' : '❌'}</span>
          <span className="font-semibold text-slate-300 w-24 shrink-0">{r.player}</span>
          <span className="text-slate-400 flex-1">{r.checkName}</span>
          {r.fireMode && (
            <span className="text-slate-500 shrink-0">
              {r.fireMode.replace('_', ' ')}{r.fireModeModifier ? ` ${r.fireModeModifier > 0 ? '+' : ''}${r.fireModeModifier}` : ''}
            </span>
          )}
          {r.hits > 0 && (
            <span className="text-amber-300 shrink-0">{r.hits} hit{r.hits === 1 ? '' : 's'}</span>
          )}
          {r.ammoSpent > 0 && (
            <span className={r.specialAmmo ? 'text-cyan-300 shrink-0' : 'text-slate-500 shrink-0'}>
              {r.ammoType || 'Ammo'} -{r.ammoSpent} ({r.ammoRemaining} left)
            </span>
          )}
          <span className="text-slate-500 shrink-0">{r.result}/{r.effective}</span>
          {r.success  && r.dos  > 0 && <span className="text-cyan-400 shrink-0">{r.dos}DoS</span>}
          {!r.success && r.dof > 0  && <span className="text-red-400 shrink-0">{r.dof}DoF</span>}
          {r.fateReroll && <span className="text-purple-300 shrink-0">✧</span>}
        </div>
      ))}
    </div>
  );
}

function deriveOutcomeRates(sim) {
  const feed = Array.isArray(sim?.roll_feed) ? sim.roll_feed : [];
  const combatRolls = feed.filter(r =>
    r.category === 'combat' || r.fireMode || String(r.checkName || '').startsWith('Attack')
  );
  const puzzleRolls = feed.filter(r =>
    r.category === 'puzzle' ||
    (!r.category && !r.fireMode && !String(r.checkName || '').startsWith('Attack') && !String(r.checkName || '').startsWith('Fear Test'))
  );
  const rate = (rolls, saved) => {
    if (rolls.length > 0) return Math.round(rolls.filter(r => r.success).length / rolls.length * 100);
    return Number.isFinite(Number(saved)) ? Number(saved) : 0;
  };
  return {
    combatRate: rate(combatRolls, sim?.combat_success_rate),
    puzzleRate: rate(puzzleRolls, sim?.puzzle_success_rate),
    combatCount: combatRolls.length,
    puzzleCount: puzzleRolls.length,
  };
}

function SimReport({ sim, onRerun, rerunning }) {
  const [rollFilter, setRollFilter] = useState('');
  if (!sim) return null;
  const cards  = Object.values(sim.player_cards || {});
  const feed   = sim.roll_feed || [];
  const scenes = sim.scene_results || [];
  const storyHooks = Array.isArray(sim.story_hooks) ? sim.story_hooks : [];
  const players = [...new Set(feed.map(r => r.player))];
  const outcomeRates = deriveOutcomeRates(sim);

  return (
    <div className="space-y-4">
      {/* Header */}
      <div className="bg-slate-800/60 rounded-xl p-4 border border-slate-700/50">
        <div className="flex flex-wrap items-center gap-3 mb-2">
          <h2 className="text-lg font-bold text-white">{sim.mission_name}</h2>
          {resultBadge(sim.result)}
          {sim.difficulty_level && difficultyBadge(sim.difficulty_level)}
          <button
            type="button"
            onClick={() => onRerun?.(sim)}
            disabled={rerunning}
            className="ml-auto rounded bg-purple-700 px-3 py-1 text-xs font-semibold text-white hover:bg-purple-600 disabled:opacity-50"
          >
            {rerunning ? 'Re-running...' : 'Re-run'}
          </button>
        </div>
        <div className="flex flex-wrap gap-4 text-sm">
          <span className="text-slate-400">📅 {new Date(sim.run_date).toLocaleString()}</span>
          <span className="text-yellow-300 font-bold">⚔ {sim.xp_earned} XP</span>
          <span className="text-slate-300">🎲 {sim.total_rolls} rolls</span>
          <span className={sim.success_rate >= 60 ? 'text-green-300' : 'text-orange-300'}>{sim.success_rate}% success</span>
          <span className={outcomeRates.combatRate >= 60 ? 'text-green-300' : 'text-orange-300'}>{outcomeRates.combatRate}% combat</span>
          <span className={outcomeRates.puzzleRate >= 60 ? 'text-green-300' : 'text-orange-300'}>{outcomeRates.puzzleRate}% puzzle/checks</span>
          <span className="text-slate-400">{sim.total_rounds} combat rounds</span>
          <span className="text-slate-400">Players: {(sim.players || []).join(', ')}</span>
          {sim.enemy_profile && <span className="text-red-300">Enemies: {sim.enemy_profile}</span>}
        </div>
      </div>

      {/* Scene Summary */}
      <div>
        <h3 className="text-sm font-semibold text-slate-300 mb-2">SCENE LOG</h3>
        {scenes.map((s, i) => <SceneRow key={i} scene={s} idx={i} />)}
      </div>

      {/* Story Hooks */}
      {storyHooks.length > 0 && (
        <div className="rounded-xl border border-cyan-700/40 bg-cyan-950/20 p-3">
          <h3 className="text-sm font-semibold text-cyan-200 mb-2">STORY HOOKS TO DRIVE NEXT BEAT</h3>
          <div className="space-y-2">
            {storyHooks.map((hook, i) => (
              <div key={`${hook.scene}-${hook.trigger}-${i}`} className="rounded border border-cyan-900/50 bg-slate-950/30 p-2 text-sm">
                <div className="flex flex-wrap items-center gap-2">
                  <span className="font-semibold text-cyan-100">{hook.scene || `Scene ${Number(hook.scene_index || 0) + 1}`}</span>
                  <span className="rounded bg-slate-800 px-1.5 py-0.5 text-[11px] uppercase text-slate-300">{hook.type || 'hook'}</span>
                  {hook.trigger && <span className="text-xs text-slate-400">{hook.trigger}</span>}
                </div>
                {hook.clue && <div className="mt-1 text-cyan-100">{hook.clue}</div>}
                {hook.gm_action && <div className="mt-1 text-amber-100">GM: {hook.gm_action}</div>}
                {hook.transition && <div className="mt-1 text-slate-300">Next: {hook.transition}</div>}
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Player Cards */}
      <div>
        <h3 className="text-sm font-semibold text-slate-300 mb-2">BATTLE-BROTHER REPORT CARDS</h3>
        <div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
          {cards.map(c => <PlayerCard key={c.name} card={c} />)}
        </div>
      </div>

      {/* Roll Feed */}
      <div>
        <div className="flex items-center gap-2 mb-2">
          <h3 className="text-sm font-semibold text-slate-300">FULL ROLL FEED</h3>
          <select
            value={rollFilter}
            onChange={e => setRollFilter(e.target.value)}
            className="ml-auto text-xs bg-slate-700 border border-slate-600 rounded px-2 py-0.5 text-slate-200"
          >
            <option value="">All players</option>
            {players.map(p => <option key={p} value={p}>{p}</option>)}
          </select>
        </div>
        <div className="bg-slate-900/40 rounded-lg p-2">
          <RollFeedPanel feed={feed} filterPlayer={rollFilter || null} />
        </div>
      </div>

      {/* End Summary */}
      <div className="grid grid-cols-1 sm:grid-cols-3 gap-2">
        <div className="rounded-xl border border-slate-700/50 bg-slate-900/40 p-3">
          <div className="text-xs uppercase tracking-wide text-slate-500">Overall</div>
          <div className={sim.success_rate >= 60 ? 'text-2xl font-black text-green-300' : 'text-2xl font-black text-orange-300'}>
            {sim.success_rate}%
          </div>
          <div className="text-xs text-slate-400">{sim.total_rolls} tracked rolls</div>
        </div>
        <div className="rounded-xl border border-red-900/50 bg-red-950/20 p-3">
          <div className="text-xs uppercase tracking-wide text-red-300/70">Combat</div>
          <div className={outcomeRates.combatRate >= 60 ? 'text-2xl font-black text-green-300' : 'text-2xl font-black text-orange-300'}>
            {outcomeRates.combatRate}%
          </div>
          <div className="text-xs text-slate-400">{outcomeRates.combatCount || 'No'} combat rolls</div>
        </div>
        <div className="rounded-xl border border-cyan-900/50 bg-cyan-950/20 p-3">
          <div className="text-xs uppercase tracking-wide text-cyan-300/70">Puzzle / Checks</div>
          <div className={outcomeRates.puzzleRate >= 60 ? 'text-2xl font-black text-green-300' : 'text-2xl font-black text-orange-300'}>
            {outcomeRates.puzzleRate}%
          </div>
          <div className="text-xs text-slate-400">{outcomeRates.puzzleCount || 'No'} puzzle/check rolls</div>
        </div>
      </div>

      {/* Findings */}
      {(sim.findings || []).length > 0 && (
        <div className="bg-amber-900/20 border border-amber-700/40 rounded-xl p-3">
          <h3 className="text-sm font-semibold text-amber-300 mb-2">⚡ FINDINGS FOR NEXT SESSION</h3>
          {sim.findings.map((f, i) => (
            <div key={i} className="text-sm text-amber-100 pl-2 mb-1">• {f}</div>
          ))}
        </div>
      )}
    </div>
  );
}

// ─── Main Tab ─────────────────────────────────────────────────────────────────
function rerunPayloadFromSim(sim) {
  const cards = sim?.player_cards || {};
  const players = Array.isArray(sim?.players) ? sim.players : [];
  const combatProfiles = Object.fromEntries(players.map(name => [
    name,
    cards?.[name]?.combatProfile || 'balanced',
  ]));
  const payload = {
    difficulty: Number(sim?.difficulty_level) || 2,
    player_names: players,
    combat_profiles: combatProfiles,
    enemy_profile: sim?.enemy_profile || 'balanced',
  };
  if (sim?.mission_id) payload.mission_id = Number(sim.mission_id);
  return payload;
}

export default function SimulationTab() {
  const [missions,    setMissions]    = useState([]);
  const [players,     setPlayers]     = useState([]);
  const [simList,     setSimList]     = useState([]);
  const [selected,    setSelected]    = useState(null);
  const [fullSim,     setFullSim]     = useState(null);
  const [running,     setRunning]     = useState(false);
  const [runError,    setRunError]    = useState('');
  const [missionId,   setMissionId]   = useState('');
  const [chosenPlayers, setChosenPlayers] = useState([]);
  const [combatProfiles, setCombatProfiles] = useState({});
  const [enemyProfile, setEnemyProfile] = useState('balanced');
  const [showConfig,  setShowConfig]  = useState(false);
  const [loadingFull, setLoadingFull] = useState(false);
  const [difficulty,  setDifficulty]  = useState(2);
  const [rerunningId, setRerunningId] = useState(null);

  const loadList = useCallback(async () => {
    try {
      const [ms, ps, ss] = await Promise.all([
        axios.get('/api/missions'),
        axios.get('/api/players'),
        axios.get('/api/simulations'),
      ]);
      setMissions(ms.data || []);
      const nonGm = (ps.data || []).filter(p => p.name !== 'gm').map(p => p.name);
      setPlayers(nonGm);
      if (!chosenPlayers.length) setChosenPlayers(nonGm);
      setSimList(ss.data || []);
    } catch {}
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => { loadList(); }, [loadList]);

  const openSim = async (sim) => {
    setSelected(sim);
    setFullSim(null);
    setLoadingFull(true);
    try {
      const { data } = await axios.get(`/api/simulations/${sim.id}`);
      setFullSim(data);
    } catch {}
    setLoadingFull(false);
  };

  const runSim = async () => {
    setRunning(true);
    setRunError('');
    try {
      const payload = { difficulty };
      if (missionId) payload.mission_id = Number(missionId);
      if (chosenPlayers.length) payload.player_names = chosenPlayers;
      payload.combat_profiles = Object.fromEntries(chosenPlayers.map(name => [name, combatProfiles[name] || 'balanced']));
      payload.enemy_profile = enemyProfile;
      const { data } = await axios.post('/api/simulations', payload);
      await loadList();
      setSelected(data);
      setFullSim(data);
      setShowConfig(false);
    } catch (err) {
      setRunError(err?.response?.data?.error || 'Simulation failed');
    }
    setRunning(false);
  };

  const rerunSim = async (sim, e) => {
    if (e) e.stopPropagation();
    setRerunningId(sim.id || 'current');
    setRunError('');
    try {
      let source = sim;
      if (!source.player_cards || !source.roll_feed) {
        const { data } = await axios.get(`/api/simulations/${sim.id}`);
        source = data;
      }
      const { data } = await axios.post('/api/simulations', rerunPayloadFromSim(source));
      await loadList();
      setSelected(data);
      setFullSim(data);
      setShowConfig(false);
    } catch (err) {
      setRunError(err?.response?.data?.error || 'Re-run failed');
    }
    setRerunningId(null);
  };

  const deleteSim = async (id, e) => {
    e.stopPropagation();
    try {
      await axios.delete(`/api/simulations/${id}`);
      if (selected?.id === id) { setSelected(null); setFullSim(null); }
      await loadList();
    } catch {}
  };

  const togglePlayer = (name) => {
    setChosenPlayers(prev =>
      prev.includes(name) ? prev.filter(n => n !== name) : [...prev, name]
    );
  };

  const setAllProfiles = (profile) => {
    setCombatProfiles(prev => ({
      ...prev,
      ...Object.fromEntries(chosenPlayers.map(name => [name, profile])),
    }));
  };

  return (
    <div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 p-4">
      <div className="max-w-7xl mx-auto">
        {/* Top bar */}
        <div className="flex flex-wrap items-center justify-between gap-3 mb-4">
          <div>
            <h1 className="text-xl font-bold text-white">⚙ Mission Simulation Lab</h1>
            <p className="text-xs text-slate-400 mt-0.5">GM-only — run statistical simulations of missions against player stat sheets</p>
          </div>
          <button
            onClick={() => setShowConfig(c => !c)}
            className="px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white font-semibold rounded-lg text-sm"
          >
            ▶ Run New Simulation
          </button>
        </div>

        {/* Config panel */}
        {showConfig && (
          <div className="bg-slate-800/70 border border-blue-700/40 rounded-xl p-4 mb-4 space-y-3">
            <h2 className="text-sm font-semibold text-blue-300">Configure Simulation</h2>
            <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
              <div>
                <label className="text-xs text-slate-400 block mb-1">Mission</label>
                <select
                  value={missionId}
                  onChange={e => setMissionId(e.target.value)}
                  className="w-full bg-slate-700 border border-slate-600 rounded px-2 py-1.5 text-slate-200 text-sm"
                >
                  <option value="">Active mission</option>
                  {missions.map(m => <option key={m.id} value={m.id}>{m.name}</option>)}
                </select>
              </div>
              <div>
                <label className="text-xs text-slate-400 block mb-1">Players</label>
                <div className="flex flex-wrap gap-2">
                  {players.map(p => (
                    <button
                      key={p}
                      onClick={() => togglePlayer(p)}
                      className={`px-2 py-0.5 rounded text-xs font-medium border transition-all ${
                        chosenPlayers.includes(p)
                          ? 'bg-blue-600/70 border-blue-500 text-white'
                          : 'bg-slate-700/50 border-slate-600 text-slate-400'
                      }`}
                    >
                      {p}
                    </button>
                  ))}
                </div>
              </div>
            </div>

            {/* Difficulty selector */}
            <div>
              <label className="text-xs text-slate-400 block mb-2">Difficulty</label>
              <div className="flex flex-wrap gap-2">
                {DIFFICULTIES.map(d => (
                  <button
                    key={d.level}
                    onClick={() => setDifficulty(d.level)}
                    title={d.desc}
                    className={`px-3 py-1.5 rounded border text-xs font-semibold transition-all ${
                      difficulty === d.level
                        ? d.badge + ' ring-2 ring-offset-1 ring-offset-slate-800 ring-white/30'
                        : 'bg-slate-700/40 border-slate-600 text-slate-400 hover:border-slate-500'
                    }`}
                  >
                    {d.name}
                  </button>
                ))}
              </div>
              {(() => {
                const sel = DIFFICULTIES.find(d => d.level === difficulty);
                return sel ? <p className="mt-1.5 text-xs text-slate-500">{sel.desc}</p> : null;
              })()}
            </div>
            <div>
              <div className="mb-2 flex flex-wrap items-center gap-2">
                <label className="text-xs text-slate-400">Combat profiles</label>
                <div className="ml-auto flex flex-wrap gap-1">
                  {COMBAT_PROFILES.map(profile => (
                    <button
                      key={profile.id}
                      type="button"
                      onClick={() => setAllProfiles(profile.id)}
                      className="rounded border border-slate-600 bg-slate-700/50 px-2 py-0.5 text-[11px] text-slate-300 hover:border-slate-500"
                      title={`Set all to ${profile.desc}`}
                    >
                      All {profile.name}
                    </button>
                  ))}
                </div>
              </div>
              <div className="grid gap-2 sm:grid-cols-2">
                {chosenPlayers.map(name => (
                  <div key={name} className="rounded border border-slate-700 bg-slate-900/40 p-2">
                    <div className="mb-1 text-xs font-semibold capitalize text-slate-200">{name}</div>
                    <div className="grid grid-cols-2 gap-1">
                      {COMBAT_PROFILES.map(profile => {
                        const active = (combatProfiles[name] || 'balanced') === profile.id;
                        return (
                          <button
                            key={profile.id}
                            type="button"
                            onClick={() => setCombatProfiles(prev => ({ ...prev, [name]: profile.id }))}
                            title={profile.desc}
                            className={`rounded border px-2 py-1 text-[11px] ${active ? 'border-cyan-500 bg-cyan-900/50 text-cyan-100' : 'border-slate-700 bg-slate-800/50 text-slate-400 hover:border-slate-600'}`}
                          >
                            {profile.name}
                          </button>
                        );
                      })}
                    </div>
                  </div>
                ))}
              </div>
              <p className="mt-1 text-xs text-slate-500">
                Use this to compare who performs best as melee, ranged, or balanced without editing their character sheet.
              </p>
            </div>
            <div>
              <label className="mb-2 block text-xs text-slate-400">Enemy profile</label>
              <div className="flex flex-wrap gap-2">
                {COMBAT_PROFILES.map(profile => {
                  const active = enemyProfile === profile.id;
                  return (
                    <button
                      key={profile.id}
                      type="button"
                      onClick={() => setEnemyProfile(profile.id)}
                      title={profile.desc}
                      className={`rounded border px-3 py-1.5 text-xs font-semibold ${active ? 'border-red-500 bg-red-900/50 text-red-100' : 'border-slate-700 bg-slate-800/50 text-slate-400 hover:border-slate-600'}`}
                    >
                      {profile.name}
                    </button>
                  );
                })}
              </div>
              <p className="mt-1 text-xs text-slate-500">
                Enemy profile controls whether enemies prefer WS melee, BS ranged, balanced mix, or strongest stat.
              </p>
            </div>
            {runError && <div className="text-red-400 text-xs">{runError}</div>}
            <div className="flex gap-2">
              <button
                onClick={runSim}
                disabled={running || !chosenPlayers.length}
                className="px-4 py-2 bg-green-700 hover:bg-green-600 disabled:opacity-50 text-white font-semibold rounded-lg text-sm"
              >
                {running ? '⏳ Running…' : '▶ Run Simulation'}
              </button>
              <button
                onClick={() => setShowConfig(false)}
                className="px-3 py-2 bg-slate-700 hover:bg-slate-600 text-slate-300 rounded-lg text-sm"
              >
                Cancel
              </button>
            </div>
          </div>
        )}

        <div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
          {/* Sidebar — simulation list */}
          <div className="lg:col-span-1">
            <h2 className="text-xs font-semibold text-slate-400 uppercase mb-2">Past Simulations ({simList.length})</h2>
            {simList.length === 0 && (
              <div className="text-slate-500 text-sm bg-slate-800/40 rounded-lg p-4 text-center">
                No simulations yet.<br/>Run one to get started.
              </div>
            )}
            <div className="space-y-1 max-h-[70vh] overflow-y-auto pr-1">
              {simList.map(sim => (
                <button
                  key={sim.id}
                  onClick={() => openSim(sim)}
                  className={`w-full text-left rounded-lg p-3 border transition-all ${
                    selected?.id === sim.id
                      ? 'bg-blue-900/40 border-blue-600/60'
                      : 'bg-slate-800/40 border-slate-700/40 hover:bg-slate-700/40'
                  }`}
                >
                  <div className="flex items-center justify-between gap-1">
                    <span className="text-xs font-medium text-slate-200 truncate">{sim.mission_name}</span>
                    <div className="flex items-center gap-1 shrink-0">
                      {sim.difficulty_level && sim.difficulty_level !== 2 && difficultyBadge(sim.difficulty_level)}
                      {resultBadge(sim.result)}
                      <button
                        onClick={e => rerunSim(sim, e)}
                        disabled={rerunningId === sim.id}
                        className="text-purple-300 hover:text-purple-100 text-xs px-1 disabled:opacity-50"
                        title="Re-run"
                      >↻</button>
                      <button
                        onClick={e => deleteSim(sim.id, e)}
                        className="text-slate-600 hover:text-red-400 text-xs px-1"
                        title="Delete"
                      >✕</button>
                    </div>
                  </div>
                  <div className="flex gap-2 text-xs text-slate-500 mt-1">
                    <span>{new Date(sim.run_date).toLocaleDateString()}</span>
                    <span className={sim.success_rate >= 60 ? 'text-green-400' : 'text-orange-400'}>{sim.success_rate}%</span>
                    <span className="text-yellow-400">{sim.xp_earned} XP</span>
                  </div>
                  <div className="text-xs text-slate-500 mt-0.5">{(sim.players || []).join(', ')}</div>
                </button>
              ))}
            </div>
          </div>

          {/* Main panel — report */}
          <div className="lg:col-span-2">
            {!selected && !showConfig && (
              <div className="text-slate-500 text-center py-20 bg-slate-800/20 rounded-xl border border-slate-700/30">
                <div className="text-4xl mb-3">⚙</div>
                <div className="text-sm">Select a past simulation or run a new one.</div>
              </div>
            )}
            {selected && loadingFull && (
              <div className="text-slate-400 text-center py-20">Loading simulation report…</div>
            )}
            {selected && !loadingFull && fullSim && (
              <SimReport sim={fullSim} onRerun={rerunSim} rerunning={rerunningId === fullSim.id || rerunningId === 'current'} />
            )}
          </div>
        </div>
      </div>
    </div>
  );
}
