// Generic (non-5e) ruleset: no death saves, negative HP allowed, down status.
const shared = require('@ttrpg/shared');
const { mockCtx } = require('./_helpers');
const {
  buildCharacterParticipant, buildMonsterParticipant,
  startEncounter, applyHpChange, deathSave, markDead, reviveParticipant,
} = shared;

function char(id, hp = 100) {
  const { participant } = buildCharacterParticipant({ id: `orig-${id}`, name: id, defaultMaxHp: hp, defaultInitMod: 2 });
  participant.id = id;
  return participant;
}
function mon(id, hp = 100) {
  const { participant } = buildMonsterParticipant({ name: id, maxHp: hp, initMod: 2 });
  participant.id = id;
  return participant;
}
function enc(ps, ruleset = 'generic') {
  return {
    name: 't', participants: ps, isStarted: false, isPaused: false,
    round: 0, currentTurnParticipantId: null, turnOrderIds: [],
    ruleset,
  };
}
const snap = (e) => JSON.parse(JSON.stringify(e));

describe('generic ruleset', () => {
  test('damage past 0 = negative HP, status down (character)', async () => {
    const { ctx } = mockCtx();
    let e = enc([char('a', 10)]);
    e = await startEncounter(e, ctx);
    const newEnc = await applyHpChange(e, 'a', 'damage', 15, ctx);
    const p = newEnc.participants.find(x => x.id === 'a');
    expect(p.currentHp).toBe(-5);
    expect(p.status).toBe('down');
  });

  test('damage exactly to 0 = status down', async () => {
    const { ctx } = mockCtx();
    let e = enc([char('a', 10)]);
    e = await startEncounter(e, ctx);
    const newEnc = await applyHpChange(e, 'a', 'damage', 10, ctx);
    const p = newEnc.participants.find(x => x.id === 'a');
    expect(p.currentHp).toBe(0);
    expect(p.status).toBe('down');
  });

  test('heal from negative = conscious', async () => {
    const { ctx } = mockCtx();
    let e = enc([char('a', 10)]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 15, ctx); // -5
    const newEnc = await applyHpChange(e, 'a', 'heal', 10, ctx);
    const p = newEnc.participants.find(x => x.id === 'a');
    expect(p.currentHp).toBe(5);
    expect(p.status).toBe('conscious');
  });

  test('heal caps at maxHp', async () => {
    const { ctx } = mockCtx();
    let e = enc([char('a', 10)]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 5, ctx); // 5
    const newEnc = await applyHpChange(e, 'a', 'heal', 20, ctx);
    const p = newEnc.participants.find(x => x.id === 'a');
    expect(p.currentHp).toBe(10);
    expect(p.status).toBe('conscious');
  });

  test('monster death = auto-inactive (generic)', async () => {
    const { ctx } = mockCtx();
    let e = enc([mon('m', 10)]);
    e = await startEncounter(e, ctx);
    const newEnc = await applyHpChange(e, 'm', 'damage', 20, ctx);
    const p = newEnc.participants.find(x => x.id.startsWith('m'));
    expect(p.currentHp).toBe(-10);
    expect(p.status).toBe('dead');
    expect(p.isActive).toBe(false);
  });

  test('monster at exactly 0 = dead + inactive (generic)', async () => {
    const { ctx } = mockCtx();
    let e = enc([mon('m', 10)]);
    e = await startEncounter(e, ctx);
    const newEnc = await applyHpChange(e, 'm', 'damage', 10, ctx);
    const p = newEnc.participants.find(x => x.id.startsWith('m'));
    expect(p.currentHp).toBe(0);
    expect(p.status).toBe('dead');
    expect(p.isActive).toBe(false);
  });

  test('deathSave throws in generic mode', async () => {
    const { ctx } = mockCtx();
    let e = enc([char('a', 10)]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 10, ctx); // down
    await expect(deathSave(e, 'a', 'fail', ctx)).rejects.toThrow();
  });

  test('revive in generic mode: dead→conscious (no stable)', async () => {
    const { ctx } = mockCtx();
    let e = enc([char('a', 10)]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 10, ctx); // down
    e = await markDead(e, 'a', ctx); // dead
    const newEnc = await reviveParticipant(e, 'a', ctx);
    const p = newEnc.participants.find(x => x.id === 'a');
    expect(p.status).toBe('conscious');
    expect(p.currentHp).toBe(0); // HP unchanged from down
  });

  test('revive generic reactivates if inactive', async () => {
    const { ctx } = mockCtx();
    let e = enc([mon('m', 10)]);
    e = await startEncounter(e, ctx);
    e = await markDead(e, e.participants[0].id, ctx); // dead+inactive
    const newEnc = await reviveParticipant(e, e.participants[0].id, ctx);
    const p = newEnc.participants[0];
    expect(p.status).toBe('conscious');
    expect(p.isActive).toBe(true);
  });

  test('markDead sets status dead (character, stays active)', async () => {
    const { ctx } = mockCtx();
    let e = enc([char('a', 10)]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 10, ctx); // down
    const newEnc = await markDead(e, 'a', ctx);
    const p = newEnc.participants.find(x => x.id === 'a');
    expect(p.status).toBe('dead');
    expect(p.isActive).toBe(true);
  });

  test('markDead on monster = dead + inactive', async () => {
    const { ctx } = mockCtx();
    let e = enc([mon('m', 10)]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'm', 'damage', 5, ctx); // down (5hp)
    const newEnc = await markDead(e, e.participants[0].id, ctx);
    const p = newEnc.participants[0];
    expect(p.status).toBe('dead');
    expect(p.isActive).toBe(false);
  });

  test('markDead on conscious = dead', async () => {
    const { ctx } = mockCtx();
    let e = enc([char('a', 10)]);
    e = await startEncounter(e, ctx);
    const newEnc = await markDead(e, 'a', ctx);
    const p = newEnc.participants.find(x => x.id === 'a');
    expect(p.status).toBe('dead');
  });

  test('damage while down = more negative (no death saves)', async () => {
    const { ctx } = mockCtx();
    let e = enc([char('a', 10)]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 15, ctx); // -5 down
    const newEnc = await applyHpChange(e, 'a', 'damage', 5, ctx);
    const p = newEnc.participants.find(x => x.id === 'a');
    expect(p.currentHp).toBe(-10);
    expect(p.status).toBe('down');
  });

  test('no unconscious condition auto-added (generic)', async () => {
    const { ctx } = mockCtx();
    let e = enc([char('a', 10)]);
    e = await startEncounter(e, ctx);
    const newEnc = await applyHpChange(e, 'a', 'damage', 10, ctx);
    const p = newEnc.participants.find(x => x.id === 'a');
    expect(p.conditions).not.toContain('unconscious');
  });

  test('down→conscious clears dead? no, dead is separate', async () => {
    // sanity: down heal to conscious, dead untouched
    const { ctx } = mockCtx();
    let e = enc([char('a', 10)]);
    e = await startEncounter(e, ctx);
    e = await applyHpChange(e, 'a', 'damage', 10, ctx); // down
    const newEnc = await applyHpChange(e, 'a', 'heal', 10, ctx);
    const p = newEnc.participants.find(x => x.id === 'a');
    expect(p.status).toBe('conscious');
  });
});
