// Pure layout of the move journal for the in-game history panel. The panel mirrors the
// score plaque above the board: one column per seat (a seat's moves fill its own column,
// top to bottom) instead of a single chronological list. Keeping the arrangement here (not
// in Game.svelte) lets it be unit-tested without a DOM; the component only renders the
// resulting cells with i18n/CSS.

import type { MoveRecord } from './model';

/** A single cell of the history grid. The grid is row-major with one column per seat: a
 * seat's k-th move sits in row k of its column. */
export interface HistoryCell {
  kind: 'play' | 'action' | 'thinking' | 'empty';
  /** Seat index this cell belongs to (its column). */
  player: number;
  /** Words formed, main word first — set for kind 'play'. */
  words?: string[];
  /** Points scored by the move — set for kind 'play'. */
  score?: number;
  /** The main word's board coordinate in classic notation (see moveCoord) — set for kind
   * 'play'. The in-game panel does not show it; the game-image export does. */
  coord?: string;
  /** The non-play action ('pass' | 'exchange' | 'resign' | 'timeout' | …) — set for kind
   * 'action'; the component localizes it. */
  action?: string;
}

/**
 * moveCoord renders a play's board coordinate in the classic notation the GCG export uses
 * (the backend's gcgPos): row-then-column (e.g. "8G") for an across play, column-then-row
 * ("H8") for a down play. Rows are 1-based numbers top to bottom, columns letters from "A"
 * left to right.
 */
export function moveCoord(mv: Pick<MoveRecord, 'dir' | 'mainRow' | 'mainCol'>): string {
  const col = String.fromCharCode(65 + mv.mainCol);
  const row = String(mv.mainRow + 1);
  return mv.dir === 'V' ? col + row : row + col;
}

/**
 * historyGrid arranges the move journal into a row-major matrix with one column per seat.
 * Each seat's moves fill its column top to bottom, so a seat's k-th move lands in row k of
 * its column; with a regular turn order the rows read as rounds. When thinkingSeat is
 * non-null, that seat's next (still empty) cell becomes a 'thinking' placeholder — the move
 * being awaited. Positions with no move are 'empty', so every row is exactly seatCount wide
 * and the table rules evenly. Pass thinkingSeat=null when the game is over or when the
 * awaited mover is the viewer themselves (the viewer's own pending cell stays empty).
 */
export function historyGrid(
  moves: MoveRecord[],
  seatCount: number,
  thinkingSeat: number | null,
): HistoryCell[][] {
  const cols: HistoryCell[][] = Array.from({ length: seatCount }, () => []);
  for (const m of moves) {
    if (m.player < 0 || m.player >= seatCount) continue; // defensive: drop an out-of-range seat
    cols[m.player].push(
      m.action === 'play'
        ? { kind: 'play', player: m.player, words: m.words, score: m.score, coord: moveCoord(m) }
        : { kind: 'action', player: m.player, action: m.action },
    );
  }

  const think =
    thinkingSeat !== null && thinkingSeat >= 0 && thinkingSeat < seatCount ? thinkingSeat : null;
  let rows = cols.reduce((n, c) => Math.max(n, c.length), 0);
  if (think !== null) rows = Math.max(rows, cols[think].length + 1);

  const grid: HistoryCell[][] = [];
  for (let r = 0; r < rows; r++) {
    const row: HistoryCell[] = [];
    for (let p = 0; p < seatCount; p++) {
      if (r < cols[p].length) row.push(cols[p][r]);
      else if (think === p && r === cols[p].length) row.push({ kind: 'thinking', player: p });
      else row.push({ kind: 'empty', player: p });
    }
    grid.push(row);
  }
  return grid;
}
