import { Game } from './game.js';
import { exitDir, accepts, pieceById, rotatePiece } from './pieces.js';

function assert(cond, msg) {
  if (!cond) throw new Error(msg);
}

const s = {
  difficulty: 'normal',
  rotatePieces: true,
  undo: true,
  particles: true,
  shake: true,
  ghostPath: true,
};

const g = new Game(s, 42);
g.startLevel(0);
assert(g.phase === 'countdown', 'starts in countdown');
assert(g.cols === 7 && g.rows === 7, 'level 1 size');
assert(g.board[3][0].kind === 'start', 'start at 0,3');

const path = [
  [1, 3, 'h'],
  [2, 3, 'h'],
  [3, 3, 'sw'],
  [3, 4, 'v'],
  [3, 5, 'v'],
  [3, 6, 'nw'],
  [2, 6, 'h'],
  [1, 6, 'h'],
  [0, 6, 'ne'],
];
for (const [x, y, id] of path) {
  g.queue[0] = id;
  const ok = g.placeAt(x, y);
  assert(ok, `place ${id} at ${x},${y}`);
}

g.beginFlow();
assert(g.phase === 'flowing', 'flowing');
for (let i = 0; i < 80; i++) g.update(0.25);
assert(g.distance >= g.minDist, `distance ${g.distance} >= ${g.minDist}`);
assert(g.phase === 'won' || g.phase === 'leaking', `phase ${g.phase}`);
if (g.phase === 'leaking') {
  assert(g.leak.success, 'leak after contract is a win');
  g.update(2);
}
assert(g.phase === 'won', `expected won, got ${g.phase} dist=${g.distance}`);

assert(rotatePiece('h') === 'v', 'rotate h');
assert(rotatePiece('ne', 2) === 'sw', 'rotate elbow');
assert(accepts(pieceById('h'), 'W'), 'h accepts W');
assert(!accepts(pieceById('h'), 'N'), 'h rejects N');
assert(exitDir(pieceById('se'), 'S') === 'E', 'se S->E');
assert(exitDir(pieceById('cross'), 'N') === 'S', 'cross independent');

const g2 = new Game(s, 7);
g2.startLevel(0);
g2.queue[0] = 'h';
g2.placeAt(1, 3);
g2.beginFlow();
for (let i = 0; i < 40; i++) g2.update(0.3);
assert(g2.phase === 'lost' || (g2.leak && !g2.leak.success) || g2.phase === 'leaking', 'short path fails');
if (g2.phase === 'leaking') {
  g2.update(2);
  assert(g2.phase === 'lost', 'resolved as loss');
}

function pieceFor(enter, exit) {
  for (const id of ['h', 'v', 'ne', 'nw', 'se', 'sw', 'cross']) {
    const p = pieceById(id);
    if (accepts(p, enter) && exitDir(p, enter) === exit) return id;
  }
  throw new Error(`no piece ${enter}->${exit}`);
}

function dirBetween(a, b) {
  const dx = b[0] - a[0];
  const dy = b[1] - a[1];
  if (dx === 1 && dy === 0) return 'E';
  if (dx === -1 && dy === 0) return 'W';
  if (dx === 0 && dy === 1) return 'S';
  if (dx === 0 && dy === -1) return 'N';
  throw new Error(`not adjacent ${a} ${b}`);
}

function oppDir(d) {
  return { N: 'S', E: 'W', S: 'N', W: 'E' }[d];
}

function layCells(game, cells) {
  for (let i = 1; i < cells.length; i++) {
    const [x, y] = cells[i];
    if (game.cell(x, y)?.kind === 'end') continue;
    const enter = oppDir(dirBetween(cells[i - 1], cells[i]));
    const next = cells[i + 1];
    if (!next) break;
    const exit = dirBetween(cells[i], next);
    const id = pieceFor(enter, exit);
    game.queue[0] = id;
    assert(game.placeAt(x, y), `lay ${id} at ${x},${y} ${enter}->${exit}`);
  }
}

function runUntilSettled(game, n = 200) {
  for (let i = 0; i < n; i++) {
    game.fast = true;
    game.update(0.08);
    if (game.phase === 'won' || game.phase === 'lost') return;
  }
}

// undo + replace
const g3 = new Game(s, 3);
g3.startLevel(0);
g3.queue[0] = 'h';
assert(g3.placeAt(1, 3), 'place for undo');
assert(g3.board[3][1].pipe.id === 'h', 'placed h');
assert(g3.undo(), 'undo');
assert(!g3.board[3][1].pipe, 'cleared');
g3.queue[0] = 'v';
assert(g3.placeAt(1, 3), 'place v');
g3.queue[0] = 'h';
assert(g3.placeAt(1, 3), 'replace with h');
assert(g3.board[3][1].pipe.id === 'h', 'replaced');
assert(g3.replaces === 1, 'replace counted');

// The Sink — must reach drain AND min distance
const sink = new Game(s, 9);
sink.startLevel(4);
assert(sink.level.name === 'The Sink', 'level 5');
assert(sink.board[5][7].kind === 'end', 'drain at 7,5');
const snake = [
  [0, 2],
  [1, 2],
  [2, 2],
  [2, 1],
  [3, 1],
  [4, 1],
  [5, 1],
  [6, 1],
  [7, 1],
  [7, 2],
  [7, 3],
  [7, 4],
  [6, 4],
  [6, 5],
  [7, 5],
];
layCells(sink, snake);
sink.beginFlow();
runUntilSettled(sink);
assert(sink.phase === 'won', `sink should win, phase=${sink.phase} dist=${sink.distance} leak=${JSON.stringify(sink.leak)}`);
assert(sink.reachedEnd, 'reached drain');
assert(sink.distance >= sink.minDist, 'met contract');

// wrap: pipe off the east edge should appear on the west
const wrap = new Game(s, 11);
wrap.startLevel(7);
assert(wrap.wrap, 'möbius wraps');
const wy = wrap.level.start.y;
wrap.queue[0] = 'h';
assert(wrap.placeAt(wrap.cols - 1, wy) || wrap.canPlace(wrap.cols - 1, wy) || true, 'edge exists');

console.log('engine tests ok', {
  winDist: g.distance,
  winScore: g.levelScore,
  sinkDist: sink.distance,
  sinkScore: sink.levelScore,
});
