import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { LocalGameRecord } from './serialize';

// An in-memory stand-in for the IndexedDB store, so list() — which reads the *persisted* records,
// not the source's in-memory cache — can be exercised in the node env (the real store is a
// best-effort no-op without IndexedDB). Declared before the vi.mock factory that closes over it.
const store = new Map<string, LocalGameRecord>();
vi.mock('./store', () => ({
  saveLocalGame: async (r: LocalGameRecord) => void store.set(r.id, r),
  getLocalGame: async (id: string) => store.get(id) ?? null,
  listLocalGames: async () => [...store.values()],
  deleteLocalGame: async (id: string) => void store.delete(id),
}));
vi.mock('../dict', () => ({
  getDawg: async () => {
    const { Dawg } = await import('../dict/dawg');
    const { readFileSync } = await import('node:fs');
    return new Dawg(new Uint8Array(readFileSync(new URL('../dict/testdata/sample_en.dawg', import.meta.url))));
  },
}));

import { LocalSource } from './source';
import type { Seat } from './serialize';

const seats: Seat[] = [
  { kind: 'human', name: 'You' },
  { kind: 'robot', name: 'Robot' },
];
const opts = (id: string, seed: bigint) => ({
  id,
  variant: 'scrabble_en' as const,
  dictVersion: 'sample',
  seed,
  multipleWords: true,
  seats,
});

beforeEach(() => store.clear());

describe('LocalSource.list', () => {
  it('is empty when no games are stored', async () => {
    expect(await new LocalSource().list()).toEqual([]);
  });

  it('returns a lobby GameView for every stored local game', async () => {
    const src = new LocalSource();
    await src.create(opts('local:a', 1n));
    await src.create(opts('local:b', 2n));
    const list = await src.list();
    expect(new Set(list.map((g) => g.id))).toEqual(new Set(['local:a', 'local:b']));
    expect(list.every((g) => g.vsAi && g.status === 'active')).toBe(true);
  });

  it('carries the human seat account id into the GameView so the client identifies "you"', async () => {
    const src = new LocalSource();
    await src.create({
      ...opts('local:me', 3n),
      seats: [
        { kind: 'human', name: 'You', accountId: 'acct-42' },
        { kind: 'robot', name: 'Robot' },
      ],
    });
    const [g] = await src.list();
    expect(g.seats[0].accountId).toBe('acct-42'); // the human's real id, matched against the session
    expect(g.seats[1].accountId).not.toBe('acct-42'); // the robot keeps a synthetic id
  });

  it('delete removes a stored local game from the list', async () => {
    const src = new LocalSource();
    await src.create(opts('local:x', 5n));
    expect((await src.list()).map((g) => g.id)).toContain('local:x');
    await src.delete('local:x');
    expect((await src.list()).map((g) => g.id)).not.toContain('local:x');
  });

  it('reconstructs a game persisted by a previous session (not in the source cache)', async () => {
    // Seed the store via one source, then list from a fresh source whose in-memory cache is empty.
    await new LocalSource().create(opts('local:old', 7n));
    const list = await new LocalSource().list();
    expect(list.map((g) => g.id)).toEqual(['local:old']);
    expect(list[0].variant).toBe('scrabble_en');
  });
});
