import { useRef, useState } from 'react';
import type { Campaign, Character, Combatant, Condition, Encounter } from '@/lib/schemas';
import { encountersRepo } from '@/lib/db/repositories';
import { newId } from '@/lib/ids';
import { createRng } from '@/lib/rng';
import { rollDice } from '@/lib/dice/notation';
import { getSystem, getConditions, type SystemId } from '@/lib/rules';
import { computeBudget, DIFFICULTY_COLOR } from '@/lib/combat/budget';
import { useCharacters } from '@/features/characters/hooks';
import { useConditionGlossary } from './useConditionGlossary';
import {
  addCombatant,
  applyDamage,
  applyHealing,
  applyInitiatives,
  currentCombatant,
  endEncounter,
  logEvent,
  moveCombatant,
  nextTurn,
  previousTurn,
  removeCombatant,
  startEncounter,
  updateCombatant,
} from '@/lib/combat/engine';
import { cn } from '@/lib/cn';
import { Button } from '@/components/ui/Button';
import { Input, Select } from '@/components/ui/Input';
import { NumberField } from '@/components/ui/NumberField';
import { EncounterTipCard } from '@/features/assistant/EncounterTipCard';

export function EncounterTracker({ encounter, campaign }: { encounter: Encounter; campaign: Campaign }) {
  const characters = useCharacters(campaign.id);
  const glossary = useConditionGlossary(campaign.system);

  const undoStack = useRef<Encounter[]>([]);
  const mutate = (fn: (e: Encounter) => Encounter) => {
    undoStack.current.push(encounter);
    if (undoStack.current.length > 50) undoStack.current.shift();
    // Transactional read-modify-write so rapid mutations can't clobber each other.
    void encountersRepo.mutate(encounter.id, fn);
  };
  const undo = () => {
    const prev = undoStack.current.pop();
    if (prev) void encountersRepo.save(prev);
  };

  const rollAllInitiative = () => {
    const rolls: Record<string, number> = {};
    for (const c of encounter.combatants) rolls[c.id] = rollDice('1d20', createRng()).total + c.initBonus;
    mutate((e) => logEvent(applyInitiatives(e, rolls), 'Rolled initiative for all combatants'));
  };

  // Difficulty budget from monster combatants vs the campaign's PCs. Once combat
  // has started, rate against the levels captured at that time (snapshot) so the
  // reading — and the assistant's history — reflects the party as it was.
  const currentLevels = characters.filter((c) => c.kind === 'pc').map((c) => c.level);
  const partyLevels = encounter.partyLevelsSnapshot?.length ? encounter.partyLevelsSnapshot : currentLevels;
  const monsters = encounter.combatants
    .filter((c) => c.kind === 'monster')
    .map((c) => ({ cr: c.cr, level: c.level }));
  const budget =
    monsters.length > 0 && partyLevels.length > 0
      ? computeBudget(campaign.system, partyLevels, monsters)
      : null;

  const current = currentCombatant(encounter);
  const isActive = encounter.status === 'active';

  return (
    <div>
      {/* Control bar */}
      <div className="mb-4 flex flex-wrap items-center gap-3 rounded-lg border border-line bg-panel p-3">
        <div>
          <div className="font-display text-lg font-semibold text-ink">{encounter.name}</div>
          <div className="text-xs text-muted">
            {isActive ? `Round ${encounter.round}` : encounter.status === 'ended' ? 'Ended' : 'Not started'}
            {current && isActive ? ` · ${current.name}'s turn` : ''}
          </div>
        </div>
        {budget && (
          <div className="rounded-md border border-line bg-surface px-3 py-1 text-center text-xs">
            <div className={cn('font-display text-sm font-semibold capitalize', DIFFICULTY_COLOR[budget.difficulty])}>
              {budget.difficulty}
            </div>
            <div className="text-muted">
              {budget.totalXp.toLocaleString()} XP · {budget.awardPerCharacter.toLocaleString()}/PC
            </div>
          </div>
        )}
        <div className="ml-auto flex flex-wrap gap-2">
          {undoStack.current.length > 0 && (
            <Button variant="ghost" onClick={undo} title="Undo last change">↺ Undo</Button>
          )}
          {encounter.combatants.length > 0 && (
            <Button variant="secondary" onClick={rollAllInitiative} title="Roll initiative for everyone">
              🎲 Roll all
            </Button>
          )}
          {!isActive && encounter.status !== 'ended' && (
            <Button
              variant="primary"
              disabled={encounter.combatants.length === 0}
              onClick={() => mutate((e) => ({
                ...startEncounter(e),
                ...(currentLevels.length ? { partyLevelsSnapshot: currentLevels } : {}),
              }))}
            >
              Start combat
            </Button>
          )}
          {isActive && (
            <>
              <Button variant="secondary" onClick={() => mutate(previousTurn)}>
                ← Prev
              </Button>
              <Button variant="primary" onClick={() => mutate(nextTurn)}>
                Next turn →
              </Button>
              <Button variant="ghost" className="text-danger" onClick={() => mutate(endEncounter)}>
                End
              </Button>
            </>
          )}
          {encounter.status === 'ended' && (
            <Button variant="secondary" onClick={() => mutate((e) => ({ ...e, status: 'planning', round: 0, turnIndex: 0 }))}>
              Reopen
            </Button>
          )}
        </div>
      </div>

      <EncounterTipCard campaign={campaign} encounter={encounter} />

      <AddCombatantBar encounter={encounter} characters={characters} onAdd={(c) => mutate((e) => addCombatant(e, c))} />

      {/* Combatant list */}
      {encounter.combatants.length === 0 ? (
        <p className="mt-6 text-sm text-muted">No combatants yet. Add characters or monsters above.</p>
      ) : (
        <ul className="mt-4 space-y-2">
          {encounter.combatants.map((c, idx) => (
            <CombatantRow
              key={c.id}
              combatant={c}
              system={campaign.system}
              glossary={glossary}
              isCurrent={isActive && idx === encounter.turnIndex}
              onChange={(patch) => mutate((e) => updateCombatant(e, c.id, patch))}
              onDamage={(amt) => mutate((e) => logEvent(updateCombatant(e, c.id, { hp: applyDamage(c, amt).hp }), `${c.name} takes ${amt} damage`))}
              onHeal={(amt) => mutate((e) => logEvent(updateCombatant(e, c.id, { hp: applyHealing(c, amt).hp }), `${c.name} heals ${amt}`))}
              onMove={(dir) => mutate((e) => moveCombatant(e, c.id, dir))}
              onRemove={() => mutate((e) => logEvent(removeCombatant(e, c.id), `${c.name} removed`))}
            />
          ))}
        </ul>
      )}

      <CombatLog log={encounter.log ?? []} onClear={() => mutate((e) => ({ ...e, log: [] }))} />
    </div>
  );
}

function CombatLog({ log, onClear }: { log: { round: number; text: string }[]; onClear: () => void }) {
  if (log.length === 0) return null;
  const recent = log.slice(-40).reverse();
  return (
    <div className="mt-6">
      <div className="mb-1 flex items-center justify-between">
        <h3 className="text-xs font-semibold uppercase tracking-wide text-muted">Combat Log</h3>
        <Button size="sm" variant="ghost" onClick={onClear}>Clear</Button>
      </div>
      <ul className="max-h-48 space-y-0.5 overflow-auto rounded-md border border-line bg-surface p-2 text-xs">
        {recent.map((l, i) => (
          <li key={i} className="text-muted">
            <span className="mr-2 text-accent/70">R{l.round}</span>
            {l.text}
          </li>
        ))}
      </ul>
    </div>
  );
}

function rollInitiativeFor(character: Character | undefined): number {
  if (!character) return rollDice('1d20', createRng()).total;
  const mod = getSystem(character.system).initiativeModifier({
    level: character.level,
    abilities: character.abilities,
    perceptionRank: character.perceptionRank,
  });
  return rollDice('1d20', createRng()).total + mod;
}

function AddCombatantBar({
  characters,
  onAdd,
}: {
  encounter: Encounter;
  characters: Character[];
  onAdd: (c: Combatant) => void;
}) {
  const [name, setName] = useState('');
  const [init, setInit] = useState(10);
  const [ac, setAc] = useState(12);
  const [hp, setHp] = useState(10);
  const [qty, setQty] = useState(1);

  const addCustom = () => {
    if (name.trim() === '') return;
    const n = Math.max(1, qty);
    for (let i = 0; i < n; i++) {
      onAdd({
        id: newId(),
        name: name.trim(), // engine auto-numbers duplicates
        kind: 'monster',
        initiative: init,
        initBonus: 0,
        ac,
        hp: { current: hp, max: hp, temp: 0 },
        conditions: [],
        notes: '',
      });
    }
    setName('');
    setQty(1);
  };

  const addCharacter = (charId: string) => {
    const ch = characters.find((c) => c.id === charId);
    if (!ch) return;
    const sys = getSystem(ch.system);
    onAdd({
      id: newId(),
      name: ch.name,
      kind: ch.kind,
      characterId: ch.id,
      initiative: rollInitiativeFor(ch),
      initBonus: sys.initiativeModifier({ level: ch.level, abilities: ch.abilities, perceptionRank: ch.perceptionRank }),
      ac: sys.baseArmorClass({ level: ch.level, abilities: ch.abilities, armorBonus: ch.armorBonus }),
      hp: { ...ch.hp },
      conditions: [],
      notes: '',
    });
  };

  return (
    <div className="space-y-2 rounded-lg border border-line bg-panel/60 p-3">
      <div className="flex flex-wrap items-end gap-2">
        <label className="flex-1 min-w-40 text-xs text-muted">
          Name
          <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="Goblin Boss" onKeyDown={(e) => e.key === 'Enter' && addCustom()} />
        </label>
        <label className="text-xs text-muted">
          Init
          <NumberField className="w-16" value={init} onChange={setInit} aria-label="Initiative" />
        </label>
        <label className="text-xs text-muted">
          AC
          <NumberField className="w-16" value={ac} min={0} onChange={setAc} aria-label="Armor class" />
        </label>
        <label className="text-xs text-muted">
          HP
          <NumberField className="w-20" value={hp} min={0} onChange={setHp} aria-label="Hit points" />
        </label>
        <label className="text-xs text-muted">
          Qty
          <NumberField className="w-14" value={qty} min={1} onChange={setQty} aria-label="Quantity" />
        </label>
        <Button variant="primary" onClick={addCustom}>
          Add
        </Button>
      </div>
      {characters.length > 0 && (
        <div className="flex items-center gap-2">
          <span className="text-xs text-muted">Add character (rolls initiative):</span>
          <Select
            className="w-auto"
            value=""
            onChange={(e) => {
              if (e.target.value) addCharacter(e.target.value);
              e.target.value = '';
            }}
          >
            <option value="">Choose…</option>
            {characters.map((c) => (
              <option key={c.id} value={c.id}>
                {c.name} (Lv {c.level})
              </option>
            ))}
          </Select>
        </div>
      )}
    </div>
  );
}

function CombatantRow({
  combatant: c,
  system,
  glossary,
  isCurrent,
  onChange,
  onDamage,
  onHeal,
  onMove,
  onRemove,
}: {
  combatant: Combatant;
  system: SystemId;
  glossary: Map<string, string>;
  isCurrent: boolean;
  onChange: (patch: Partial<Combatant>) => void;
  onDamage: (amt: number) => void;
  onHeal: (amt: number) => void;
  onMove: (dir: -1 | 1) => void;
  onRemove: () => void;
}) {
  const [delta, setDelta] = useState(0);
  const dead = c.hp.current <= 0;

  return (
    <li
      className={
        'rounded-lg border bg-panel p-3 ' +
        (isCurrent ? 'border-accent ring-1 ring-accent/40' : 'border-line') +
        (dead ? ' opacity-60' : '')
      }
    >
      <div className="flex flex-wrap items-center gap-3">
        <div className="flex flex-col items-center">
          <NumberField className="w-14" value={c.initiative} onChange={(initiative) => onChange({ initiative })} aria-label={`${c.name} initiative`} />
          <span className="mt-0.5 text-[10px] uppercase text-muted">init</span>
        </div>

        <div className="min-w-32 flex-1">
          <div className="flex items-center gap-2">
            <span className="font-medium text-ink">{c.name}</span>
            <span className="rounded bg-elevated px-1.5 text-[10px] uppercase text-muted">{c.kind}</span>
            {dead && <span className="text-xs font-semibold text-danger">DOWN</span>}
          </div>
          {c.conditions.length > 0 && (
            <div className="mt-1 flex flex-wrap gap-1">
              {c.conditions.map((cond, i) => (
                <button
                  key={`${cond.name}-${i}`}
                  className="rounded-full bg-warning/15 px-2 py-0.5 text-xs text-warning hover:line-through"
                  title={glossary.get(cond.name.toLowerCase()) || 'Click to remove'}
                  onClick={() => onChange({ conditions: c.conditions.filter((_, j) => j !== i) })}
                >
                  {cond.name}
                  {cond.value ? ` ${cond.value}` : ''}
                  {cond.duration ? ` (${cond.duration}r)` : ''} ✕
                </button>
              ))}
            </div>
          )}
        </div>

        {/* HP */}
        <div className="text-center">
          <div className="text-sm">
            <span className={dead ? 'text-danger' : 'text-ink'}>{c.hp.current}</span>
            <span className="text-muted">/{c.hp.max}</span>
            {c.hp.temp > 0 && <span className="text-info"> +{c.hp.temp}</span>}
          </div>
          <div className="mt-1 flex items-center gap-1">
            <NumberField className="w-14" value={delta} min={0} onChange={setDelta} aria-label={`${c.name} HP amount`} />
            <Button size="sm" variant="danger" disabled={delta <= 0} onClick={() => { onDamage(delta); setDelta(0); }} title="Apply damage">
              Dmg
            </Button>
            <Button size="sm" variant="secondary" disabled={delta <= 0} onClick={() => { onHeal(delta); setDelta(0); }} title="Heal">
              Heal
            </Button>
          </div>
        </div>

        <div className="text-center text-sm">
          <div className="text-muted">AC</div>
          <NumberField className="w-12" value={c.ac} min={0} onChange={(ac) => onChange({ ac })} aria-label={`${c.name} AC`} />
        </div>

        <div className="flex flex-col gap-1">
          <Button size="icon" variant="ghost" onClick={() => onMove(-1)} aria-label="Move up" title="Move up">↑</Button>
          <Button size="icon" variant="ghost" onClick={() => onMove(1)} aria-label="Move down" title="Move down">↓</Button>
        </div>
        <Button size="icon" variant="ghost" className="text-danger" onClick={onRemove} aria-label={`Remove ${c.name}`}>✕</Button>
      </div>

      <div className="mt-2">
        <ConditionPicker
          system={system}
          existing={c.conditions}
          onAdd={(cond) => onChange({ conditions: [...c.conditions, cond] })}
        />
      </div>
    </li>
  );
}

function ConditionPicker({
  system,
  existing,
  onAdd,
}: {
  system: SystemId;
  existing: Condition[];
  onAdd: (cond: Condition) => void;
}) {
  const [sel, setSel] = useState('');
  const [value, setValue] = useState(1);
  const [dur, setDur] = useState(0);
  const [custom, setCustom] = useState('');

  const conditions = getConditions(system);
  const taken = new Set(existing.map((c) => c.name));
  const selected = conditions.find((c) => c.name === sel);
  const isCustom = sel === '__custom__';

  const reset = () => {
    setSel('');
    setValue(1);
    setDur(0);
    setCustom('');
  };

  const withDuration = (cond: Condition): Condition => (dur > 0 ? { ...cond, duration: dur } : cond);

  const addCustom = () => {
    if (custom.trim() === '') return;
    onAdd(withDuration({ name: custom.trim() }));
    reset();
  };

  const addValued = () => {
    if (!selected) return;
    onAdd(withDuration({ name: selected.name, value }));
    reset();
  };

  // Non-valued presets add immediately on selection for a snappy "tag" feel.
  const onSelect = (name: string) => {
    setSel(name);
    if (name === '' || name === '__custom__') return;
    const def = conditions.find((c) => c.name === name);
    if (def && !def.valued) {
      onAdd(withDuration({ name: def.name }));
      reset();
    }
  };

  return (
    <div className="flex flex-wrap items-center gap-1.5">
      <Select
        className="h-8 w-auto py-0 text-xs"
        aria-label="Add condition"
        value={sel}
        onChange={(e) => onSelect(e.target.value)}
      >
        <option value="">+ Condition…</option>
        {conditions.map((cd) => (
          <option key={cd.name} value={cd.name} disabled={taken.has(cd.name)}>
            {cd.name}
            {cd.valued ? ' (#)' : ''}
            {taken.has(cd.name) ? ' ✓' : ''}
          </option>
        ))}
        <option value="__custom__">Custom…</option>
      </Select>

      <label className="flex items-center gap-1 text-[11px] text-muted" title="Auto-expires after N rounds (0 = indefinite)">
        <NumberField className="w-12" value={dur} min={0} onChange={setDur} aria-label="Condition duration in rounds" />
        rds
      </label>

      {selected?.valued && (
        <>
          <NumberField className="w-14" value={value} min={1} onChange={setValue} aria-label="Condition value" />
          <Button size="sm" variant="primary" onClick={addValued}>Add</Button>
        </>
      )}

      {isCustom && (
        <>
          <Input
            className="h-8 w-40 text-xs"
            autoFocus
            value={custom}
            placeholder="Condition name"
            onChange={(e) => setCustom(e.target.value)}
            onKeyDown={(e) => e.key === 'Enter' && addCustom()}
          />
          <Button size="sm" variant="primary" onClick={addCustom}>Add</Button>
        </>
      )}
    </div>
  );
}
