// Regression test: full round must rotate through ALL active participants exactly once.
// Audit of 100-round replay found 124 skips + 78 dupes (round 1 already missing Fighter
// before any coverage action). nextTurn has core bug, not just coverage-path issue.
//
// Rewritten for async turn.js API: funcs are awaited, take ctx, write own logs.

const shared = require('@ttrpg/shared');
const { mockCtx } = require('./_helpers');
const { startEncounter, nextTurn, makeParticipant } = shared;

function p(id, initiative, extra = {}) {
  return makeParticipant({
    id, name: id, type: 'monster',
    initiative, maxHp: 20, currentHp: 20,
    ...extra,
  });
}

function enc(ps) {
  return {
    name: 'T', participants: ps,
    isStarted: false, isPaused: false,
    round: 0, currentTurnParticipantId: null, turnOrderIds: [],
  };
}

describe('round rotation integrity', () => {
  test('3 participants: one full round visits each exactly once', async () => {
    const { ctx } = mockCtx();
    const ps = [p('a', 20), p('b', 15), p('c', 10)];
    let e = enc(ps);
    e = await startEncounter(e, ctx);

    const startOrder = e.turnOrderIds.slice();
    const visited = [e.currentTurnParticipantId];

    // advance (len-1) turns: visits remaining participants, round NOT yet wrapped.
    for (let i = 0; i < startOrder.length - 1; i++) {
      e = await nextTurn(e, ctx);
      visited.push(e.currentTurnParticipantId);
    }

    expect(e.round).toBe(1); // still round 1
    const uniq = new Set(visited);
    expect(uniq.size).toBe(startOrder.length); // each exactly once
    expect(visited.length).toBe(startOrder.length);
  });

  test('8 participants (replay shape): one full round visits each exactly once', async () => {
    const { ctx } = mockCtx();
    const ps = [
      p('Goblin1', 12), p('Wolf', 13), p('Merchant', 8), p('OrcBoss', 11),
      p('Goblin2', 12), p('Fighter', 14), p('Rogue', 15), p('Cleric', 10),
    ];
    let e = enc(ps);
    e = await startEncounter(e, ctx);

    const startOrder = e.turnOrderIds.slice();
    const visited = [e.currentTurnParticipantId];
    for (let i = 0; i < startOrder.length - 1; i++) {
      e = await nextTurn(e, ctx);
      visited.push(e.currentTurnParticipantId);
    }

    expect(e.round).toBe(1);
    const uniq = new Set(visited);
    expect(uniq.size).toBe(startOrder.length);
    expect(visited.length).toBe(startOrder.length);
  });

  test('multiple rounds: each round visits each participant exactly once', async () => {
    const { ctx } = mockCtx();
    const ps = [p('a', 20), p('b', 15), p('c', 10), p('d', 5)];
    let e = enc(ps);
    e = await startEncounter(e, ctx);

    const startOrder = e.turnOrderIds.slice();
    const expectedRound = e.round;

    // capture exactly one full round (current + len-1 advances), no wrap yet.
    const visited = [e.currentTurnParticipantId];
    for (let i = 0; i < startOrder.length - 1; i++) {
      e = await nextTurn(e, ctx);
      visited.push(e.currentTurnParticipantId);
    }
    const uniq = new Set(visited);
    expect(uniq.size).toBe(startOrder.length);
    expect(e.round).toBe(expectedRound);
  });
});

describe('round rotation with mid-round state changes', () => {
  const { toggleParticipantActive, addParticipant, removeParticipant, reorderParticipants, applyHpChange } = shared;

  test('toggle a participant inactive mid-round, others still each visited once', async () => {
    const { ctx } = mockCtx();
    const ps = [p('a', 20), p('b', 15), p('c', 10), p('d', 5)];
    let e = enc(ps);
    e = await startEncounter(e, ctx);
    const startOrder = e.turnOrderIds.slice();

    const visited = [e.currentTurnParticipantId];
    e = await nextTurn(e, ctx); visited.push(e.currentTurnParticipantId);
    // now mark 'a' inactive (already took its turn)
    e = await toggleParticipantActive(e, 'a', ctx);
    e = await nextTurn(e, ctx); visited.push(e.currentTurnParticipantId);
    e = await nextTurn(e, ctx); visited.push(e.currentTurnParticipantId);
    // round should wrap, but 'a' inactive so only b,c,d visited
    const visitedActive = visited.filter(id => id !== 'a');
    const uniq = new Set(visitedActive);
    expect(uniq.size).toBe(startOrder.length - 1); // b,c,d each once
  });

  test('reactivate inactive participant mid-round, it gets a turn this round', async () => {
    const { ctx } = mockCtx();
    const ps = [p('a', 20), p('b', 15), p('c', 10), p('d', 5)];
    let e = enc(ps);
    // start with 'c' inactive
    e.participants = e.participants.map(p => p.id === 'c' ? { ...p, isActive: false } : p);
    e = await startEncounter(e, ctx);
    // 1-list: c stays in slot (inactive), skipped by nextTurn
    expect(e.turnOrderIds).toEqual(['a', 'b', 'c', 'd']);
    expect(e.currentTurnParticipantId).toBe('a'); // c inactive, a first

    // advance one turn, then reactivate c
    e = await nextTurn(e, ctx); // b
    e = await toggleParticipantActive(e, 'c', ctx);

    // continue rotation - c should now be reachable
    const visited = [e.currentTurnParticipantId];
    for (let i = 0; i < e.turnOrderIds.length; i++) {
      e = await nextTurn(e, ctx);
      visited.push(e.currentTurnParticipantId);
    }
    expect(visited).toContain('c');
  });

  test('addParticipant mid-round: new participant gets turn this round or next', async () => {
    const { ctx } = mockCtx();
    const ps = [p('a', 20), p('b', 15), p('c', 10)];
    let e = enc(ps);
    e = await startEncounter(e, ctx);
    const startOrder = e.turnOrderIds.slice();

    e = await nextTurn(e, ctx); // advance one
    // add new participant
    const newP = p('x', 25);
    e = await addParticipant(e, newP, ctx);

    // finish round - original 3 should still each get exactly one turn
    const visited = [startOrder[0], e.currentTurnParticipantId];
    while (e.round === 1) {
      e = await nextTurn(e, ctx);
      visited.push(e.currentTurnParticipantId);
      if (visited.length > 20) break; // safety
    }
    const originals = visited.filter(id => ['a','b','c'].includes(id));
    const uniq = new Set(originals);
    expect(uniq.size).toBe(3);
  });

  test('reorderParticipants mid-round keeps rotation valid', async () => {
    const { ctx } = mockCtx();
    const ps = [p('a', 20), p('b', 15), p('c', 15), p('d', 5)]; // b,c same init (15)
    let e = enc(ps);
    e = await startEncounter(e, ctx);
    const startOrder = e.turnOrderIds.slice();

    e = await nextTurn(e, ctx);
    // reorder: swap b,c (same initiative)
    e = await reorderParticipants(e, 'b', 'c', ctx);

    const visited = [startOrder[0], e.currentTurnParticipantId];
    for (let i = 0; i < startOrder.length; i++) {
      e = await nextTurn(e, ctx);
      visited.push(e.currentTurnParticipantId);
    }
    const uniq = new Set(visited);
    expect(uniq.size).toBeGreaterThanOrEqual(startOrder.length);
  });
});
