import { beforeEach, describe, expect, it } from 'vitest';
import { clearGameCache, getCachedGame, setCachedDraft, setCachedGame } from './gamecache';
import type { GameView, MoveRecord, StateView } from './model';

function gameView(id: string): GameView {
  return {
    id,
    variant: 'scrabble_en',
    dictVersion: 'v1',
    vsAi: false,
    unreadChat: false,
    unreadMessages: false,
    kind: 0,
    status: 'active',
    players: 2,
    toMove: 0,
    turnTimeoutSecs: 300,
    multipleWordsPerTurn: true,
    noRepeatWords: false,
    moveCount: 0,
    endReason: '',
    lastActivityUnix: 0,
    seats: [],
  };
}

function view(id: string, rack: string[] = ['A', 'B']): StateView {
  return { game: gameView(id), seat: 0, rack, bagLen: 50, hintsRemaining: 1 };
}

function move(player: number): MoveRecord {
  return { player, action: 'play', dir: 'H', mainRow: 7, mainCol: 7, tiles: [], words: ['AB'], count: 0, score: 10, total: 10 };
}

beforeEach(() => clearGameCache());

describe('setCachedGame', () => {
  it('stores state, history and draft', () => {
    setCachedGame('g1', view('g1'), [move(0)], '{"x":1}');
    const c = getCachedGame('g1');
    expect(c?.view.game.id).toBe('g1');
    expect(c?.moves).toHaveLength(1);
    expect(c?.draft).toBe('{"x":1}');
  });

  it('leaves draft undefined when none is given for a new entry', () => {
    setCachedGame('g1', view('g1'), []);
    expect(getCachedGame('g1')?.draft).toBeUndefined();
  });

  it('preserves the cached draft when draft is omitted (the live-event delta path)', () => {
    setCachedGame('g1', view('g1'), [], 'DRAFT');
    // A delta advances view+moves without refetching the draft; it must not be dropped.
    setCachedGame('g1', view('g1', ['C', 'D']), [move(1)]);
    const c = getCachedGame('g1');
    expect(c?.draft).toBe('DRAFT'); // preserved
    expect(c?.view.rack).toEqual(['C', 'D']); // view advanced
    expect(c?.moves).toHaveLength(1);
  });

  it('clears the draft when passed an empty string (a committed move)', () => {
    setCachedGame('g1', view('g1'), [], 'DRAFT');
    setCachedGame('g1', view('g1'), [move(0)], '');
    expect(getCachedGame('g1')?.draft).toBe('');
  });
});

describe('setCachedDraft', () => {
  it('updates only the draft of an already-cached game', () => {
    setCachedGame('g1', view('g1', ['A']), [move(0)], 'OLD');
    setCachedDraft('g1', 'NEW');
    const c = getCachedGame('g1');
    expect(c?.draft).toBe('NEW');
    expect(c?.view.rack).toEqual(['A']); // unchanged
    expect(c?.moves).toHaveLength(1);
  });

  it('is a no-op when the game is not cached', () => {
    setCachedDraft('absent', 'X');
    expect(getCachedGame('absent')).toBeUndefined();
  });
});

describe('clearGameCache', () => {
  it('drops every cached game', () => {
    setCachedGame('g1', view('g1'), []);
    setCachedGame('g2', view('g2'), []);
    clearGameCache();
    expect(getCachedGame('g1')).toBeUndefined();
    expect(getCachedGame('g2')).toBeUndefined();
  });
});
