#!/usr/bin/env bun
/**
 * Serve a browser preview of the Tui overlay at production frame sizes.
 * Rebuilds the web bundle, then hosts overlay/dist + overlay/preview.html.
 *
 *   bun run preview:overlay
 *   bun run preview:overlay -- --game=connect4
 *   → http://127.0.0.1:4173/?game=connect4
 */

import { spawn } from "node:child_process";
import { existsSync } from "node:fs";
import { join, normalize, relative, resolve } from "node:path";

const ROOT = join(import.meta.dir, "..");
const DIST = join(ROOT, "overlay", "dist");
const PREVIEW_HTML = join(ROOT, "overlay", "preview.html");
const HOST = process.env.GAMEPIGEON_PREVIEW_HOST ?? "127.0.0.1";
const DEFAULT_PORT = Number(process.env.GAMEPIGEON_PREVIEW_PORT ?? 4173);

function parseArgs(argv: string[]): { game?: string; variation?: string; scores?: string; open: boolean; help: boolean } {
  let game: string | undefined;
  let variation: string | undefined;
  let scores: string | undefined;
  let open = true;
  let help = false;
  for (const arg of argv) {
    if (arg === "--help" || arg === "-h") help = true;
    else if (arg === "--no-open") open = false;
    else if (arg === "--open") open = true;
    else if (arg.startsWith("--game=")) game = arg.slice("--game=".length) || undefined;
    else if (arg.startsWith("--variation=")) variation = arg.slice("--variation=".length) || undefined;
    else if (arg.startsWith("--scores=")) scores = arg.slice("--scores=".length) || undefined;
  }
  return { game, variation, scores, open, help };
}

function printHelp(): void {
  console.log(`Usage: bun run preview:overlay [-- --game=<id>] [-- --variation=<id>] [-- --scores=demo] [-- --no-open]

Rebuilds the overlay web bundle and serves a browser preview at the real
Tauri frame sizes (520×740 compact / 720×780 expanded).

Options (after --):
  --game=<id>         Deep-link into a game (e.g. connect4)
  --variation=<id>    Optional variation id (default: classic)
  --scores=demo       Populate preview-only sample leaderboard runs
  --open / --no-open  Open the default browser (default: open)
  -h, --help          Show this help

Env:
  GAMEPIGEON_PREVIEW_HOST  bind host (default 127.0.0.1)
  GAMEPIGEON_PREVIEW_PORT  preferred port (default 4173; falls back if busy)
`);
}

function run(command: string, args: string[]): Promise<number> {
  return new Promise((resolveExit, reject) => {
    const child = spawn(command, args, { cwd: ROOT, stdio: "inherit" });
    child.on("error", reject);
    child.on("exit", (code) => resolveExit(code ?? 1));
  });
}

function openBrowser(url: string): void {
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
  const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
  spawn(opener, args, { stdio: "ignore", detached: true }).unref();
}

const args = parseArgs(process.argv.slice(2));
if (args.help) {
  printHelp();
  process.exit(0);
}

const buildCode = await run("bun", ["run", "build:overlay:web"]);
if (buildCode !== 0) process.exit(buildCode);

if (!existsSync(join(DIST, "index.html"))) {
  console.error(`Missing ${join(DIST, "index.html")} — expected overlay web assets.`);
  process.exit(1);
}
if (!existsSync(PREVIEW_HTML)) {
  console.error(`Missing ${PREVIEW_HTML}`);
  process.exit(1);
}

const previewBytes = await Bun.file(PREVIEW_HTML).bytes();

function distFile(pathname: string): string | null {
  const cleaned = normalize(pathname).replace(/^(\.\.[/\\])+/, "");
  const filePath = resolve(DIST, cleaned.replace(/^[/\\]+/, ""));
  const rel = relative(DIST, filePath);
  if (!rel || rel.startsWith("..") || rel.includes("..")) return null;
  return filePath;
}

const fetchHandler = {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);
    if (url.pathname === "/" || url.pathname === "/preview.html") {
      return new Response(previewBytes, {
        headers: {
          "Content-Type": "text/html; charset=utf-8",
          "Cache-Control": "no-store",
        },
      });
    }

    const filePath = distFile(url.pathname);
    if (!filePath) return new Response("Forbidden", { status: 403 });
    const file = Bun.file(filePath);
    if (!(await file.exists())) {
      return new Response("Not found", { status: 404 });
    }
    return new Response(file, {
      headers: { "Cache-Control": "no-store" },
    });
  },
};

function startServer(port: number) {
  return Bun.serve({
    hostname: HOST,
    port,
    ...fetchHandler,
  });
}

let server: ReturnType<typeof Bun.serve>;
try {
  server = startServer(DEFAULT_PORT);
} catch (error) {
  const code = (error as { code?: string }).code;
  if (code !== "EADDRINUSE") throw error;
  console.warn(`Port ${DEFAULT_PORT} is busy — trying the next free port…`);
  server = startServer(0);
}

const query = new URLSearchParams();
if (args.game) query.set("game", args.game);
if (args.variation) query.set("variation", args.variation);
if (args.scores) query.set("scores", args.scores);
const suffix = query.toString() ? `/?${query}` : "/";
const base = `http://${server.hostname}:${server.port}`;
const url = `${base}${suffix}`;

console.log(`Tui browser preview → ${url}`);
if (!args.game) console.log(`Deep-link example     → ${base}/?game=connect4`);
console.log("Ctrl+C to stop.");

if (args.open) openBrowser(url);
