import { describe, expect, it } from 'vitest';
import { historyGrid, moveCoord, type HistoryCell } from './history';
import type { MoveRecord } from './model';

function play(player: number, words: string[], score: number): MoveRecord {
  return { player, action: 'play', dir: 'H', mainRow: 7, mainCol: 7, tiles: [], words, count: words.length, score, total: 0 };
}
function action(player: number, act: 'pass' | 'exchange' | 'resign' | 'timeout'): MoveRecord {
  return { player, action: act, dir: '', mainRow: 0, mainCol: 0, tiles: [], words: [], count: 0, score: 0, total: 0 };
}
function kinds(grid: HistoryCell[][]): string[][] {
  return grid.map((row) => row.map((c) => c.kind));
}

describe('historyGrid', () => {
  it('lays moves into per-seat columns and marks the awaited opponent as thinking', () => {
    const moves = [play(0, ['ДОМ'], 12), action(1, 'pass'), play(0, ['ОСА', 'ОМ'], 12)];
    const grid = historyGrid(moves, 2, 1); // seat 1 is to move

    expect(kinds(grid)).toEqual([
      ['play', 'action'],
      ['play', 'thinking'],
    ]);
    expect(grid[0][0]).toMatchObject({ kind: 'play', words: ['ДОМ'], score: 12 });
    expect(grid[1][0]).toMatchObject({ kind: 'play', words: ['ОСА', 'ОМ'], score: 12 });
    expect(grid[0][1]).toMatchObject({ kind: 'action', action: 'pass' });
    expect(grid[1][1]).toMatchObject({ kind: 'thinking', player: 1 });
  });

  it('renders no thinking cell when thinkingSeat is null (game over / own turn)', () => {
    const moves = [play(0, ['ДОМ'], 12), action(1, 'pass'), play(0, ['ОСА'], 6)];
    const grid = historyGrid(moves, 2, null);

    expect(kinds(grid)).toEqual([
      ['play', 'action'],
      ['play', 'empty'],
    ]);
  });

  it('fills ragged columns with empty cells (a seat that stopped moving)', () => {
    const moves = [
      play(0, ['ДОМ'], 12),
      play(1, ['КОТ'], 10),
      action(2, 'resign'),
      play(0, ['ОСА'], 6),
      play(1, ['ЛИС'], 8),
    ];
    const grid = historyGrid(moves, 3, null);

    expect(kinds(grid)).toEqual([
      ['play', 'play', 'action'],
      ['play', 'play', 'empty'],
    ]);
    expect(grid[0][2]).toMatchObject({ kind: 'action', action: 'resign' });
  });

  it('shows a lone thinking cell for the first mover on an empty journal', () => {
    expect(kinds(historyGrid([], 2, 0))).toEqual([['thinking', 'empty']]);
  });

  it('produces an empty grid for an empty journal with no awaited mover', () => {
    expect(historyGrid([], 2, null)).toEqual([]);
  });

  it('ignores moves with an out-of-range seat', () => {
    const grid = historyGrid([play(5, ['X'], 1), play(0, ['ДОМ'], 12)], 2, null);
    expect(kinds(grid)).toEqual([['play', 'empty']]);
    expect(grid[0][0]).toMatchObject({ words: ['ДОМ'] });
  });

  it('stamps a play cell with its classic coordinate', () => {
    const grid = historyGrid([play(0, ['ДОМ'], 12)], 1, null);
    expect(grid[0][0]).toMatchObject({ kind: 'play', coord: '8H' });
  });
});

describe('moveCoord', () => {
  // The classic notation the GCG export uses (backend gcgPos): an across play is
  // row-then-column ("8G"), a down play column-then-row ("H8").
  it('puts the row number first for an across play', () => {
    expect(moveCoord({ dir: 'H', mainRow: 7, mainCol: 6 })).toBe('8G');
  });

  it('puts the column letter first for a down play', () => {
    expect(moveCoord({ dir: 'V', mainRow: 7, mainCol: 7 })).toBe('H8');
  });

  it('covers the board corners', () => {
    expect(moveCoord({ dir: 'H', mainRow: 0, mainCol: 0 })).toBe('1A');
    expect(moveCoord({ dir: 'V', mainRow: 14, mainCol: 14 })).toBe('O15');
  });
});
