// Undo roundtrip for DEATH paths: damage to 0 HP (dying/dead), deathSave,
// stabilize, revive, dead-monster auto-deactivate. Each must restore full
// prior state including status, death-save counters, conditions (unconscious),
// and isActive.
//
// Pattern: apply op (writes encounter + log) -> read last log -> expandUndo
// against newEnc -> merge -> deepEqual original snapshot.
const shared = require('@ttrpg/shared');
const { mockCtx } = require('./_helpers');
const { expandUndo } = shared;
const {
  makeParticipant, startEncounter,
  applyHpChange, deathSave, stabilizeParticipant, reviveParticipant,
} = shared;

function char(id, hp = 100) {
  return makeParticipant({
    id, name: id, type: 'character',
    initiative: 10, maxHp: hp, currentHp: hp,
  });
}
function mon(id, hp = 100) {
  return makeParticipant({
    id, name: id, type: 'monster',
    initiative: 10, maxHp: hp, currentHp: hp,
  });
}
function enc(ps) {
  return { name:'t', participants:ps, isStarted:false, isPaused:false,
    round:0, currentTurnParticipantId:null, turnOrderIds:[] };
}
const snap = (e) => JSON.parse(JSON.stringify(e));

function lastUndo(storage, enc) {
  const logs = storage.logs();
  const last = logs[logs.length - 1];
  expect(last.undo).toBeTruthy();
  const expanded = expandUndo(last, enc);
  expect(expanded).toBeTruthy();
  return expanded.updates;
}

describe('death-path undo roundtrip', () => {
  test('damage to 0 HP (dying) undo restores status + unconscious condition', async () => {
    const { storage, ctx } = mockCtx();
    let e = enc([char('a')]);
    e = await startEncounter(e, ctx);
    const before = snap(e);
    const newEnc = await applyHpChange(e, 'a', 'damage', 100, ctx);
    const p = newEnc.participants.find(x => x.id === 'a');
    expect(p.status).toBe('dying');
    expect(p.conditions).toContain('unconscious');
    const undo = lastUndo(storage, newEnc);
    const after = { ...newEnc, ...undo };
    expect(snap(after)).toEqual(before);
  });

  test('damage while dying undo restores status + failures + conditions', async () => {
    const { storage, ctx } = mockCtx();
    let e = enc([char('a')]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 100, ctx); // -> dying
    const before = snap(e);
    const newEnc = await applyHpChange(e, 'a', 'damage', 5, ctx); // +1 fail
    expect(newEnc.participants.find(x => x.id === 'a').deathSaveFailures).toBe(1);
    const undo = lastUndo(storage, newEnc);
    const after = { ...newEnc, ...undo };
    expect(snap(after)).toEqual(before);
  });

  test('damage while stable undo restores stable + reset failures', async () => {
    const { storage, ctx } = mockCtx();
    let e = enc([char('a')]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 100, ctx); // -> dying
    e = await stabilizeParticipant(e, 'a', ctx); // -> stable
    const before = snap(e);
    const newEnc = await applyHpChange(e, 'a', 'damage', 1, ctx); // -> dying 1 fail
    const undo = lastUndo(storage, newEnc);
    const after = { ...newEnc, ...undo };
    expect(snap(after)).toEqual(before);
  });

  test('massive damage (dead) undo restores conscious', async () => {
    const { storage, ctx } = mockCtx();
    let e = enc([char('a', 50)]);
    e = await startEncounter(e, ctx);
    const before = snap(e);
    const newEnc = await applyHpChange(e, 'a', 'damage', 200, ctx);
    expect(newEnc.participants.find(x => x.id === 'a').status).toBe('dead');
    const undo = lastUndo(storage, newEnc);
    const after = { ...newEnc, ...undo };
    expect(snap(after)).toEqual(before);
  });

  test('dead monster auto-deactivate undo restores active', async () => {
    const { storage, ctx } = mockCtx();
    let e = enc([mon('a')]);
    e = await startEncounter(e, ctx);
    // first make it dead+inactive
    e = await applyHpChange(e, 'a', 'damage', 100, ctx); // -> dead + inactive
    // second damage at 0 while dead: no-op (returns same enc) but if it had
    // repaired a stale active-dead monster it writes deactivate_dead_monster.
    // Force the repair path: set stale isActive=true then damage.
    e = { ...e, participants: e.participants.map(p => p.id === 'a' ? { ...p, isActive: true } : p) };
    const before = snap(e); // stale active-dead = state right before the op
    const newEnc = await applyHpChange(e, 'a', 'damage', 1, ctx);
    expect(newEnc.participants.find(x => x.id === 'a').isActive).toBe(false);
    const undo = lastUndo(storage, newEnc);
    const after = { ...newEnc, ...undo };
    expect(snap(after)).toEqual(before);
  });

  test('deathSave fail undo restores dying + counters + conditions', async () => {
    const { storage, ctx } = mockCtx();
    let e = enc([char('a')]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 100, ctx); // -> dying
    const before = snap(e);
    const r = await deathSave(e, 'a', 'fail', ctx);
    expect(r.enc.participants.find(x => x.id === 'a').deathSaveFailures).toBe(1);
    const undo = lastUndo(storage, r.enc);
    const after = { ...r.enc, ...undo };
    expect(snap(after)).toEqual(before);
  });

  test('deathSave nat1 (2 fails) undo restores prior counters', async () => {
    const { storage, ctx } = mockCtx();
    let e = enc([char('a')]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 100, ctx); // -> dying
    const before = snap(e);
    const r = await deathSave(e, 'a', 'nat1', ctx);
    expect(r.enc.participants.find(x => x.id === 'a').deathSaveFailures).toBe(2);
    const undo = lastUndo(storage, r.enc);
    const after = { ...r.enc, ...undo };
    expect(snap(after)).toEqual(before);
  });

  test('deathSave nat20 (conscious) undo restores dying + unconscious', async () => {
    const { storage, ctx } = mockCtx();
    let e = enc([char('a')]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 100, ctx); // -> dying
    const before = snap(e);
    const r = await deathSave(e, 'a', 'nat20', ctx);
    expect(r.enc.participants.find(x => x.id === 'a').status).toBe('conscious');
    expect(r.enc.participants.find(x => x.id === 'a').currentHp).toBe(1);
    const undo = lastUndo(storage, r.enc);
    const after = { ...r.enc, ...undo };
    expect(snap(after)).toEqual(before);
  });

  test('deathSave to stable undo restores dying + unconscious', async () => {
    const { storage, ctx } = mockCtx();
    let e = enc([char('a')]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 100, ctx); // -> dying
    e = await deathSave(e, 'a', 'success', ctx).then(x => x.enc);
    e = await deathSave(e, 'a', 'success', ctx).then(x => x.enc);
    const before = snap(e); // 2 successes, dying
    const r = await deathSave(e, 'a', 'success', ctx); // -> stable
    expect(r.enc.participants.find(x => x.id === 'a').status).toBe('stable');
    const undo = lastUndo(storage, r.enc);
    const after = { ...r.enc, ...undo };
    expect(snap(after)).toEqual(before);
  });

  test('stabilize undo restores dying + unconscious + counters', async () => {
    const { storage, ctx } = mockCtx();
    let e = enc([char('a')]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 100, ctx); // -> dying
    e = await deathSave(e, 'a', 'fail', ctx).then(x => x.enc); // 1 fail
    const before = snap(e);
    const newEnc = await stabilizeParticipant(e, 'a', ctx);
    expect(newEnc.participants.find(x => x.id === 'a').status).toBe('stable');
    const undo = lastUndo(storage, newEnc);
    const after = { ...newEnc, ...undo };
    expect(snap(after)).toEqual(before);
  });

  test('revive undo restores dead + isActive + counters', async () => {
    const { storage, ctx } = mockCtx();
    let e = enc([char('a')]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 200, ctx); // -> dead (active)
    const before = snap(e);
    const newEnc = await reviveParticipant(e, 'a', ctx);
    expect(newEnc.participants.find(x => x.id === 'a').status).toBe('stable');
    const undo = lastUndo(storage, newEnc);
    const after = { ...newEnc, ...undo };
    expect(snap(after)).toEqual(before);
  });
});
