// Play-direction inference, ported from the backend engine (direction.go
// resolveDirection/runLength and domain.go (*Game).playDirection, v1.1.1). The
// client sends tiles without an orientation; the server infers it, so the local
// move preview must infer the same one or its words and score would diverge.

import { validatePlay, Horizontal, Vertical, type Board, type Ruleset, type Placement, type Direction, type Dict } from './validate';

// runLength mirrors engine.runLength: how many cells the word through (row, col)
// along dir spans, counting the target square plus the filled runs either side.
function runLength(b: Board, row: number, col: number, dir: Direction): number {
  let dr = 0;
  let dc = 1;
  if (dir === Vertical) {
    dr = 1;
    dc = 0;
  }
  let n = 1;
  for (let r = row - dr, c = col - dc; b.filled(r, c); r -= dr, c -= dc) n++;
  for (let r = row + dr, c = col + dc; b.filled(r, c); r += dr, c += dc) n++;
  return n;
}

/**
 * resolveDirection infers a play's orientation from geometry alone, mirroring
 * engine.resolveDirection: two or more tiles by the line they share; a single tile
 * by the axis along which it abuts existing tiles, preferring the longer word and
 * horizontal on a tie; a tile abutting nothing falls back to horizontal.
 */
export function resolveDirection(b: Board, placements: Placement[]): Direction {
  if (placements.length >= 2) {
    const row = placements[0].row;
    for (let i = 1; i < placements.length; i++) {
      if (placements[i].row !== row) return Vertical;
    }
    return Horizontal;
  }
  if (placements.length === 1) {
    const p = placements[0];
    const h = runLength(b, p.row, p.col, Horizontal);
    const v = runLength(b, p.row, p.col, Vertical);
    if (v >= 2 && v > h) return Vertical;
    if (h >= 2) return Horizontal;
    if (v >= 2) return Vertical;
  }
  return Horizontal;
}

/**
 * playDirection resolves the orientation for a live play, mirroring engine
 * (*Game).playDirection. It is the geometric resolution, except a single tile under
 * the single-word rule (ignoreCrossWords) is ambiguous — its two orientations may
 * differ in legality — so both are tried through the validator and the
 * higher-scoring legal one is kept (horizontal breaks a tie).
 */
export function playDirection(b: Board, rs: Ruleset, dict: Dict, placements: Placement[]): Direction {
  const geo = resolveDirection(b, placements);
  if (placements.length !== 1 || !rs.ignoreCrossWords) {
    return geo;
  }
  let best = geo;
  let found = false;
  let bestScore = 0;
  for (const dir of [Horizontal, Vertical] as Direction[]) {
    const r = validatePlay(b, rs, dict, dir, placements);
    if (!r.legal || !r.move) continue;
    if (!found || r.move.score > bestScore) {
      best = dir;
      found = true;
      bestScore = r.move.score;
    }
  }
  return best;
}
