import { CAMPAIGN_LEVELS } from "../src/game/content/levels.ts";
import { LaserPuzzleGame } from "../src/game/simulation/LaserPuzzleGame.ts";
import type { LevelData } from "../src/game/simulation/types";

type ActionName = "U" | "D" | "L" | "R" | "Q" | "E" | "Z" | "X";

const MOVE_DT = 1 / 2.9; // Matches MOVE_SPEED in LaserPuzzleGame.
const ROTATION_STEP = 90;

const ACTIONS: Array<{ name: ActionName; apply: (sim: LaserPuzzleGame) => boolean }> = [
  { name: "U", apply: (sim) => sim.movePlayerAxis(0, -1, MOVE_DT) },
  { name: "D", apply: (sim) => sim.movePlayerAxis(0, 1, MOVE_DT) },
  { name: "L", apply: (sim) => sim.movePlayerAxis(-1, 0, MOVE_DT) },
  { name: "R", apply: (sim) => sim.movePlayerAxis(1, 0, MOVE_DT) },
  { name: "Q", apply: (sim) => sim.rotateAdjacent(-ROTATION_STEP) },
  { name: "E", apply: (sim) => sim.rotateAdjacent(ROTATION_STEP) },
  { name: "Z", apply: (sim) => sim.repaintAdjacentPrism(-1) },
  { name: "X", apply: (sim) => sim.repaintAdjacentPrism(1) }
];

interface ParentStep {
  prev: string | null;
  action: ActionName | null;
}

function simKey(sim: LaserPuzzleGame): string {
  const raw = sim as unknown as {
    player: { x: number; y: number };
    devices: Array<{ id: string; rot: number; tint?: string }>;
    portalOpen: boolean;
  };

  const devices = [...raw.devices]
    .sort((a, b) => a.id.localeCompare(b.id))
    .map((device) => `${device.id}:${device.rot}:${device.tint ?? "-"}`)
    .join(";");

  const px = Math.round(raw.player.x * 2);
  const py = Math.round(raw.player.y * 2);
  return `${px},${py}|${raw.portalOpen ? 1 : 0}|${devices}`;
}

function isSolved(sim: LaserPuzzleGame): boolean {
  return Boolean((sim as unknown as { campaignComplete: boolean }).campaignComplete);
}

function cloneSim(source: LaserPuzzleGame, level: LevelData): LaserPuzzleGame {
  const src = source as unknown as {
    levelIndex: number;
    player: { x: number; y: number };
    devices: Array<{ id: string; type: string; x: number; y: number; rot: number; tint?: string }>;
    beams: Array<{ from: { x: number; y: number }; to: { x: number; y: number }; color: string }>;
    detectorMaskByKey: Map<string, number>;
    portalOpen: boolean;
    campaignComplete: boolean;
    statusText: string;
  };

  const clone = new LaserPuzzleGame([level]) as unknown as typeof src;
  clone.levelIndex = src.levelIndex;
  clone.player = { ...src.player };
  clone.devices = src.devices.map((device) => ({ ...device }));
  clone.beams = src.beams.map((beam) => ({ from: { ...beam.from }, to: { ...beam.to }, color: beam.color }));
  clone.detectorMaskByKey = new Map(src.detectorMaskByKey);
  clone.portalOpen = src.portalOpen;
  clone.campaignComplete = src.campaignComplete;
  clone.statusText = src.statusText;
  return clone as unknown as LaserPuzzleGame;
}

function reconstructPath(steps: Map<string, ParentStep>, endKey: string): ActionName[] {
  const out: ActionName[] = [];
  let key: string | null = endKey;

  while (key) {
    const step = steps.get(key);
    if (!step) {
      break;
    }
    if (step.action) {
      out.push(step.action);
    }
    key = step.prev;
  }

  out.reverse();
  return out;
}

function solveLevel(level: LevelData, maxStates = 35_000): { solvable: boolean; path?: ActionName[]; explored: number } {
  const start = new LaserPuzzleGame([level]);
  const startKey = simKey(start);

  if (isSolved(start)) {
    return { solvable: true, path: [], explored: 1 };
  }

  const queue: Array<{ key: string; sim: LaserPuzzleGame }> = [{ key: startKey, sim: start }];
  let queueIndex = 0;
  const steps = new Map<string, ParentStep>([[startKey, { prev: null, action: null }]]);

  let explored = 0;
  while (queueIndex < queue.length) {
    const node = queue[queueIndex];
    queueIndex += 1;
    if (!node) {
      break;
    }
    explored += 1;
    if (explored > maxStates) {
      break;
    }

    for (const action of ACTIONS) {
      const next = cloneSim(node.sim, level);
      const changed = action.apply(next);
      if (!changed) {
        continue;
      }

      const nextKey = simKey(next);
      if (steps.has(nextKey)) {
        continue;
      }

      steps.set(nextKey, { prev: node.key, action: action.name });

      if (isSolved(next)) {
        return { solvable: true, path: reconstructPath(steps, nextKey), explored };
      }

      queue.push({ key: nextKey, sim: next });
    }
  }

  return { solvable: false, explored };
}

let failed = 0;
for (let i = 0; i < CAMPAIGN_LEVELS.length; i += 1) {
  const level = CAMPAIGN_LEVELS[i];
  const result = solveLevel(level);
  if (result.solvable) {
    const preview = (result.path ?? []).slice(0, 50).join("");
    console.log(`OK   L${i + 1} ${level.id} steps=${result.path?.length ?? 0} explored=${result.explored} path=${preview}${(result.path?.length ?? 0) > 50 ? "..." : ""}`);
  } else {
    failed += 1;
    console.log(`FAIL L${i + 1} ${level.id} explored=${result.explored}`);
  }
}

if (failed > 0) {
  process.exitCode = 1;
}
