import { describe, expect, it } from 'vitest';
import { formedGeometry, badgePlacement } from './formed';
import type { Board } from './board';

const SIZE = 15;

// board builds a 15×15 empty board with the given committed tiles placed.
function board(tiles: Array<[number, number, string]> = []): Board {
  const b: Board = [];
  for (let r = 0; r < SIZE; r++) {
    const row: (Board[number][number])[] = [];
    for (let c = 0; c < SIZE; c++) row.push(null);
    b.push(row);
  }
  for (const [r, c, letter] of tiles) b[r][c] = { letter, blank: false };
  return b;
}

const cells = (g: ReturnType<typeof formedGeometry>) => [...(g?.cells ?? new Set())].sort();

describe('formedGeometry', () => {
  it('returns null with nothing staged', () => {
    expect(formedGeometry(board(), [], true, 'H')).toBeNull();
  });

  it('returns null when the staged tiles are off the named line', () => {
    // Two tiles on different rows cannot form a horizontal play; only an illegal shape does this,
    // and the caller never previews one.
    const g = formedGeometry(board(), [
      { row: 7, col: 7 },
      { row: 8, col: 8 },
    ], true, 'H');
    expect(g).toBeNull();
  });

  it('finds a fresh horizontal word with no cross words', () => {
    const g = formedGeometry(board(), [
      { row: 7, col: 7 },
      { row: 7, col: 8 },
    ], true, 'H');
    expect(g?.main).toEqual({ row: 7, col: 7, dir: 'H', len: 2 });
    expect(cells(g)).toEqual(['7,7', '7,8']);
  });

  it('finds a fresh vertical word', () => {
    const g = formedGeometry(board(), [
      { row: 7, col: 7 },
      { row: 8, col: 7 },
    ], true, 'V');
    expect(g?.main).toEqual({ row: 7, col: 7, dir: 'V', len: 2 });
    expect(cells(g)).toEqual(['7,7', '8,7']);
  });

  it('extends a committed tile into the main word (committed cell in the set)', () => {
    // A committed A at (7,7); a lone tile to its right forms the two-letter main word.
    const g = formedGeometry(board([[7, 7, 'A']]), [{ row: 7, col: 8 }], true, 'H');
    expect(g?.main).toEqual({ row: 7, col: 7, dir: 'H', len: 2 });
    expect(cells(g)).toEqual(['7,7', '7,8']);
  });

  it('collects a cross word through a staged tile, committed cells included', () => {
    // Committed X above and Y below (7,8) leave a one-cell gap; a horizontal play through it also
    // completes the vertical X-_-Y cross word.
    const g = formedGeometry(board([[6, 8, 'X'], [8, 8, 'Y']]), [
      { row: 7, col: 7 },
      { row: 7, col: 8 },
    ], true, 'H');
    expect(g?.main).toEqual({ row: 7, col: 7, dir: 'H', len: 2 });
    expect(cells(g)).toEqual(['6,8', '7,7', '7,8', '8,8']);
  });

  it('omits cross words under the single-word rule (multipleWords = false)', () => {
    // The same board as the cross-word case, but a single-word game: the engine ignores the vertical
    // X-_-Y run (and it need not be a real word), so the geometry must highlight only the main
    // (horizontal) word — the reported "БО lights up green in a one-word game" bug.
    const g = formedGeometry(board([[6, 8, 'X'], [8, 8, 'Y']]), [
      { row: 7, col: 7 },
      { row: 7, col: 8 },
    ], false, 'H');
    expect(g?.main).toEqual({ row: 7, col: 7, dir: 'H', len: 2 });
    expect(cells(g)).toEqual(['7,7', '7,8']);
  });

  it('takes the named axis as the main word for a lone connecting tile', () => {
    // (7,8) joins a length-3 horizontal (cols 6-8) and a length-4 vertical (rows 5-8). The engine
    // resolved the play as vertical, so the vertical is the main word and the horizontal becomes a
    // cross word.
    const g = formedGeometry(
      board([
        [7, 6, 'A'],
        [7, 7, 'B'],
        [5, 8, 'C'],
        [6, 8, 'D'],
        [8, 8, 'E'],
      ]),
      [{ row: 7, col: 8 }],
      true,
      'V',
    );
    expect(g?.main).toEqual({ row: 5, col: 8, dir: 'V', len: 4 });
    expect(cells(g)).toEqual(['5,8', '6,8', '7,6', '7,7', '7,8', '8,8']);
  });

  it('follows the engine over the longer run for a lone tile under the single-word rule', () => {
    // The reported bug: a committed horizontal DOM (row 7, cols 7-9) and a committed X at (8,10). A
    // lone tile at (7,10) plays the vertical two-letter word down column 10 — it merely abuts DOM's
    // last letter without crossing it. The horizontal run through the tile is the longer one (4
    // cells) but is no word at all, so the engine resolves the play vertical; the highlight must
    // follow, not light up the four-cell horizontal run.
    const g = formedGeometry(
      board([
        [7, 7, 'D'],
        [7, 8, 'O'],
        [7, 9, 'M'],
        [8, 10, 'X'],
      ]),
      [{ row: 7, col: 10 }],
      false,
      'V',
    );
    expect(g?.main).toEqual({ row: 7, col: 10, dir: 'V', len: 2 });
    expect(cells(g)).toEqual(['7,10', '8,10']);
  });
});

describe('badgePlacement', () => {
  it('anchors a mid-board horizontal word at the last letter, top-right', () => {
    expect(badgePlacement({ row: 7, col: 5, dir: 'H', len: 3 })).toEqual({ row: 7, col: 7, corner: 'tr' });
  });

  it('anchors a right-edge horizontal word at the first letter, bottom-left', () => {
    expect(badgePlacement({ row: 7, col: 12, dir: 'H', len: 3 })).toEqual({ row: 7, col: 12, corner: 'bl' });
  });

  it('anchors a top-edge horizontal word at the first letter, bottom-left', () => {
    expect(badgePlacement({ row: 0, col: 5, dir: 'H', len: 3 })).toEqual({ row: 0, col: 5, corner: 'bl' });
  });

  it('anchors a left-edge horizontal word at the last letter, top-right', () => {
    expect(badgePlacement({ row: 7, col: 0, dir: 'H', len: 3 })).toEqual({ row: 7, col: 2, corner: 'tr' });
  });

  it('anchors a bottom-edge horizontal word at the last letter, top-right', () => {
    expect(badgePlacement({ row: 14, col: 5, dir: 'H', len: 3 })).toEqual({ row: 14, col: 7, corner: 'tr' });
  });

  it('anchors a mid-board vertical word at the first letter, top-right', () => {
    expect(badgePlacement({ row: 5, col: 7, dir: 'V', len: 3 })).toEqual({ row: 5, col: 7, corner: 'tr' });
  });

  it('anchors a top-edge vertical word at the last letter, bottom-left', () => {
    expect(badgePlacement({ row: 0, col: 7, dir: 'V', len: 3 })).toEqual({ row: 2, col: 7, corner: 'bl' });
  });

  it('anchors a right-edge vertical word at the last letter, bottom-left', () => {
    expect(badgePlacement({ row: 5, col: 14, dir: 'V', len: 3 })).toEqual({ row: 7, col: 14, corner: 'bl' });
  });

  it('anchors a full-width horizontal word at the last letter, top-right (clamped by the board)', () => {
    expect(badgePlacement({ row: 7, col: 0, dir: 'H', len: 15 })).toEqual({ row: 7, col: 14, corner: 'tr' });
  });
});
