// DEPRECATED — DO NOT USE.
// Random simulation gave false 0-violations while replay (exact ops)
// reproduced real bugs. Replay-mirror approach = duplicate work.
// Kept for now in case parts reusable. Will delete once log analyzer
// (scratch/) + unit tests cover the ground.
//
// Prefer: replay-combat.js dumps turnOrderIds per turn, log analyzer
// finds dupes/skips from real run. Unit tests lock confirmed bugs.
//
// To revive: delete this early-return block below.
if (require.main === module) {
  console.error('audit-state.js DEPRECATED. See header comment.');
  process.exit(0);
}

// === original (below) — exploratory bug-finder, kept for reference ===
// Expanded bug-finder: runs combat through pure turn.js, audits invariant
// checks per round across multiple bug classes (not just rotation).
// NOT a unit test (Math.random, exploratory). Unit tests lock known bugs.
//
// Bug classes audited:
//   1. Rotation integrity  (skip/dupe per round)  — BUG-1, BUG-3
//   2. HP invariants       (0<=hp<=max, no NaN)
//   3. Condition toggles   (consistent, applied/removed)
//   4. isActive consistency (dead=inactive, alive=active after ops)
//   5. turnOrderIds        (no dup ids, no orphan/dead ids, subset of active)
//   6. currentTurn         (valid id, in turnOrderIds, isActive)
//   7. deathSave counter   (0<=saves<=3, reset on revive)
//   8. removeParticipant   (turnOrderIds updated, currentTurn updated)
//   9. Undo                (every op.patch has .log.undo; roundtrip restores)
//
// Run: node scripts/audit-state.js [rounds]

'use strict';
const shared = require('../../shared');
const {
  makeParticipant, startEncounter, nextTurn, togglePause,
  addParticipant, updateParticipant, removeParticipant,
  toggleParticipantActive, applyHpChange, deathSave,
  toggleCondition, reorderParticipants, endEncounter,
} = shared;

const ROUNDS = parseInt(process.argv[2], 10) || 100;

function p(id, init, extra = {}) {
  return makeParticipant({
    id, name: id, type: 'monster',
    initiative: init, maxHp: 200, currentHp: 200,
    ...extra,
  });
}
function enc(ps) {
  return { name:'a', participants:ps, isStarted:false, isPaused:false,
    round:0, currentTurnParticipantId:null, turnOrderIds:[] };
}

const ps = [
  p('c1', 14, { type:'character' }), p('c2', 10, { type:'character' }),
  p('c3', 15, { type:'character' }), p('m1', 12), p('m2', 12),
  p('m3', 11, { maxHp:500, currentHp:500 }), p('m4', 13),
  p('n1', 8, { maxHp:150, currentHp:150, isNpc:true }),
];

let e = enc(ps);
const violations = [];

function check(label, cond, detail) {
  if (!cond) violations.push({ label, detail, round: e.round, state: snap(e) });
}

function snap(x) {
  return JSON.stringify({
    round: x.round, isStarted: x.isStarted, isPaused: x.isPaused,
    current: x.currentTurnParticipantId,
    order: x.turnOrderIds,
    hp: x.participants.map(p => `${p.id}:${p.currentHp}/${p.maxHp}${p.isActive===false?'-': ''}`),
    dead: x.participants.filter(p => p.currentHp <= 0).map(p => p.id),
    inactive: x.participants.filter(p => p.isActive === false).map(p => p.id),
  });
}

// start
e = { ...e, ...startEncounter(e).patch };
let totalTurns = 0;

for (let roundN = 1; roundN <= ROUNDS; roundN++) {
  const startRound = e.round;

  // ops (mirror replay)
  const actor = e.participants.find(p => p.id === e.currentTurnParticipantId);
  if (actor) {
    const foes = e.participants.filter(p => p.id !== actor.id && p.currentHp > 0 && p.isActive !== false);
    if (foes.length > 0) {
      const tgt = foes[Math.floor(Math.random() * foes.length)];
      const dmg = 1 + Math.floor(Math.random() * 5);
      try { e = { ...e, ...applyHpChange(e, tgt.id, 'damage', dmg).patch }; } catch (err) {}
    }
    if (actor.name === 'c2' && totalTurns % 2 === 0) {
      const wounded = e.participants.filter(p => p.currentHp > 0 && p.currentHp < p.maxHp)
        .sort((a,b)=>(a.currentHp/a.maxHp)-(b.currentHp/b.maxHp));
      if (wounded.length > 0) {
        try { e = { ...e, ...applyHpChange(e, wounded[0].id, 'heal', 2+Math.floor(Math.random()*5)).patch }; } catch (err) {}
      }
    }
  }
  if (totalTurns % 4 === 0) {
    const living = e.participants.filter(p => p.currentHp > 0 && p.isActive !== false);
    if (living.length > 0) {
      const tgt = living[Math.floor(Math.random()*living.length)];
      try { e = { ...e, ...toggleCondition(e, tgt.id, 'stunned').patch }; } catch (err) {}
    }
  }
  if (totalTurns % 9 === 0) {
    const living = e.participants.filter(p => p.currentHp > 0);
    if (living.length > 0) {
      const tgt = living[Math.floor(Math.random()*living.length)];
      try { e = { ...e, ...toggleParticipantActive(e, tgt.id).patch }; } catch (err) {}
    }
  }
  if (totalTurns % 5 === 0) {
    const dead = e.participants.find(p => p.currentHp <= 0);
    if (dead) { try { e = { ...e, ...removeParticipant(e, dead.id).patch }; } catch (err) {} }
  }
  // mid-round revive: DM reactivates a downed participant's turn (mirrors
  // replay-combat.js + real play). Triggers same path as revive-between-rounds
  // but INSIDE rotation — where BUG-5 lives.
  if (totalTurns % 7 === 0 && totalTurns > 0) {
    const down = e.participants.find(p => p.currentHp <= 0 || p.isActive === false);
    if (down) {
      try {
        if (down.isActive === false) e = { ...e, ...toggleParticipantActive(e, down.id).patch };
        e = { ...e, ...applyHpChange(e, down.id, 'heal', down.maxHp).patch };
      } catch (err) {}
    }
  }
  if (totalTurns % 10 === 0 && totalTurns > 0) {
    const np = makeParticipant({ id: `r${totalTurns}`, name:`R${totalTurns}`, type:'monster', initiative:9, maxHp:100, currentHp:100 });
    try { e = { ...e, ...addParticipant(e, np).patch }; } catch (err) {}
  }
  if (totalTurns % 12 === 0) {
    try { e = { ...e, ...togglePause(e).patch }; } catch (err) {}
    try { e = { ...e, ...togglePause(e).patch }; } catch (err) {}
  }

  // advance until round wraps or cap
  const cap = (e.participants.length + 4) * 2;
  let guard = 0;
  const seenThisRound = [];
  while (e.round === startRound && guard < cap) {
    if (e.currentTurnParticipantId) seenThisRound.push(e.currentTurnParticipantId);
    if (e.isPaused) { check('advance-while-paused', false, 'paused at advance'); break; }
    let t;
    try { t = nextTurn(e); } catch (err) { check('nextTurn-throws', false, err.message); break; }
    e = { ...e, ...t.patch };
    if (e.round !== startRound) break;
    totalTurns++;
    guard++;
    if (!e.isStarted) break;
  }

  // === audits ===
  // 1. rotation (this round, before wrap)
  const uniq = new Set(seenThisRound);
  check('rotation-dupes', uniq.size >= seenThisRound.length,
    `seen ${seenThisRound.length} uniq ${uniq.size}: ${JSON.stringify(seenThisRound)}`);

  // 2. HP invariants
  for (const p of e.participants) {
    check(`hp-valid:${p.id}`, typeof p.currentHp === 'number' && !isNaN(p.currentHp) && p.currentHp >= 0 && p.currentHp <= p.maxHp,
      `hp=${p.currentHp} max=${p.maxHp}`);
  }
  // 3. isActive consistency: dead should be inactive (after applyHpChange)
  for (const p of e.participants) {
    check(`dead-inactive:${p.id}`, p.currentHp > 0 || p.isActive === false,
      `hp=${p.currentHp} isActive=${p.isActive}`);
  }
  // 4. turnOrderIds no dup
  const orderUniq = new Set(e.turnOrderIds);
  check('turnOrder-no-dup', orderUniq.size === e.turnOrderIds.length,
    `order ${JSON.stringify(e.turnOrderIds)}`);
  // 5. turnOrderIds all active
  for (const id of e.turnOrderIds) {
    const p = e.participants.find(x => x.id === id);
    check(`turnOrder-active:${id}`, p && p.isActive !== false,
      `isActive=${p && p.isActive}`);
  }
  // 6. currentTurn valid
  if (e.isStarted && e.currentTurnParticipantId) {
    const ct = e.participants.find(x => x.id === e.currentTurnParticipantId);
    check('currentTurn-exists', !!ct, `id=${e.currentTurnParticipantId}`);
    if (ct) check('currentTurn-active', ct.isActive !== false, `isActive=${ct.isActive}`);
  }
  // 7. deathSave range
  for (const p of e.participants) {
    check(`deathSaves-range:${p.id}`, (p.deathSaves||0) >= 0 && (p.deathSaves||0) <= 3,
      `saves=${p.deathSaves}`);
    if (p.currentHp > 0 && !p.isDying) {
      check(`deathSaves-reset:${p.id}`, (p.deathSaves||0) === 0,
        `alive but saves=${p.deathSaves}`);
    }
  }
  // 8. remove: turnOrderIds doesn't contain removed ids
  const ids = new Set(e.participants.map(p => p.id));
  for (const id of e.turnOrderIds) {
    check(`turnOrder-present:${id}`, ids.has(id), `orphan id in order`);
  }

  if (!e.isStarted) { console.log('encounter ended early'); break; }

  // revive dead each round (sustain combat)
  const dead = e.participants.filter(p => p.currentHp <= 0 || p.isActive === false);
  for (const d of dead) {
    try {
      if (d.isActive === false) e = { ...e, ...toggleParticipantActive(e, d.id).patch };
      e = { ...e, ...applyHpChange(e, d.id, 'heal', d.maxHp).patch };
    } catch (err) {}
  }
}

// 9. undo: every op returns log.undo
const undoOps = ['startEncounter','nextTurn','applyHpChange','toggleCondition','toggleParticipantActive','addParticipant','removeParticipant','togglePause'];
console.log('\n=== undo support (static check) ===');
console.log('checked via log fields at runtime; this harness discards logs');

console.log(`\n=== VIOLATIONS: ${violations.length} / ${ROUNDS} rounds ===`);
const byLabel = {};
for (const v of violations) byLabel[v.label] = (byLabel[v.label]||0) + 1;
const sorted = Object.entries(byLabel).sort((a,b)=>b[1]-a[1]);
for (const [label, count] of sorted) console.log(`  ${count}x ${label}`);
console.log('\nfirst 5 examples:');
for (const v of violations.slice(0,5)) console.log(`  r${v.round} ${v.label}: ${v.detail}`);
