// Test logEvent normalizer — canonical shape for UI/download/replay/analyze.
// New design: legacy logs (no type) DROPPED. Not in pipeline. Kept only for
// human scroll in LogsView (raw). download/copy/replay/analyze = typed only.
const { normalizeEvent, serializeEvents } = require('../logEvent.js');

describe('logEvent normalizer', () => {
  test('new schema passes through', () => {
    const raw = {
      id: 'l1', ts: 1000, type: 'next_turn', message: 'Bob turn',
      encounterId: 'e1', encounterName: 'Enc', encounterPath: 'encounters/e1',
      participantId: 'p1', participantName: 'Bob',
      delta: { wrapped: true, prevRound: 1 },
      undo: { currentTurnParticipantId: 'p0', round: 1, turnOrderIds: ['p0','p1'] },
      undone: false, snapshot: { round: 2, currentTurnParticipantId: 'p1', turnOrderIds: ['p1'], activeIds: ['p1'] },
    };
    const e = normalizeEvent(raw);
    expect(e.id).toBe('l1');
    expect(e.type).toBe('next_turn');
    expect(e.snapshot.round).toBe(2);
    expect(e.delta.wrapped).toBe(true);
    expect(e.undo.round).toBe(1);
    expect(e.participantName).toBe('Bob');
  });

  test('old bloated schema (payload/undo_payload) lifts undo, drops payload', () => {
    const raw = {
      id: 'l1b', ts: 1100, type: 'damage', message: 'dmg',
      encounterPath: 'encounters/e1',
      payload: { participants: [{ id: 'p1', currentHp: 30 }] },
      undo_payload: { encounterPath: 'encounters/e1', updates: { round: 1 }, redo: { round: 2 } },
      snapshot: { round: 2, currentTurnParticipantId: 'p1', turnOrderIds: ['p1'], activeIds: ['p1'] },
    };
    const e = normalizeEvent(raw);
    expect(e.type).toBe('damage');
    expect(e.undo).toBeTruthy();
    expect(e.delta).toBeNull();
  });

  test('legacy (no type) dropped → null', () => {
    const raw = { id: 'l2', timestamp: 500, message: 'old', encounterName: 'Enc', undo: { encounterPath: 'enc/e1', updates: {} } };
    expect(normalizeEvent(raw)).toBeNull();
  });

  test('explicit unknown type dropped → null', () => {
    expect(normalizeEvent({ id: 'l3', type: 'unknown' })).toBeNull();
  });

  test('no type at all dropped → null', () => {
    expect(normalizeEvent({ id: 'l4' })).toBeNull();
    expect(normalizeEvent({ path: 'logs/abc' })).toBeNull();
  });

  test('null in null out', () => {
    expect(normalizeEvent(null)).toBeNull();
  });

  test('serialize drops all-legacy input → empty', () => {
    const raw = [
      { id: 'c', timestamp: 300 },
      { id: 'a', timestamp: 100 },
      { id: 'b', timestamp: 200 },
    ];
    expect(JSON.parse(serializeEvents(raw))).toEqual([]);
  });

  test('serialize sorts typed events ascending by ts', () => {
    const raw = [
      { id: 'c', type: 'next_turn', ts: 300 },
      { id: 'a', type: 'start_encounter', ts: 100 },
      { id: 'b', type: 'damage', ts: 200 },
    ];
    const arr = JSON.parse(serializeEvents(raw));
    expect(arr.map(e => e.id)).toEqual(['a', 'b', 'c']);
  });

  test('serialize filters nulls + legacy, keeps typed', () => {
    const arr = JSON.parse(serializeEvents([null, { id: 'x', type: 'damage' }, { id: 'legacy' }, null]));
    expect(arr).toHaveLength(1);
    expect(arr[0].id).toBe('x');
  });
});
