import { spawn } from "node:child_process";
import { resolve } from "node:path";
import { startReportServer } from "@invariantum/report";
import type { ServeOptions } from "../parse-args.js";
import type { IoContext } from "./verify.js";

export type ServeSession = { url: string; close: () => Promise<void> };

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

export async function startServeSession(options: ServeOptions, io: IoContext): Promise<ServeSession> {
  const reportDir = resolve(io.cwd, options.reportDir);
  const server = await startReportServer({
    reportDir,
    host: "127.0.0.1",
    port: options.port ?? 0,
  });
  io.stderr.write(`report path: ${reportDir}\n`);
  io.stderr.write(`local url: ${server.url}\n`);
  if (!options.noOpen && io.env.CI === undefined) {
    try {
      openBrowser(server.url);
    } catch (error) {
      io.stderr.write(`browser open failed: ${error instanceof Error ? error.message : String(error)}\n`);
    }
  }
  return { url: server.url, close: () => server.close() };
}

const STOP_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"] as const;

async function serveUntilStopped(io: IoContext): Promise<void> {
  io.stderr.write("serving report; press Ctrl+C to stop\n");
  await new Promise<void>((resolvePromise) => {
    const stop = (): void => {
      for (const signal of STOP_SIGNALS) process.off(signal, stop);
      resolvePromise();
    };
    for (const signal of STOP_SIGNALS) process.once(signal, stop);
  });
}

export async function runServe(options: ServeOptions, io: IoContext): Promise<0 | 2> {
  let session: ServeSession;
  try {
    session = await startServeSession(options, io);
  } catch (error) {
    io.stderr.write(`serve error: ${error instanceof Error ? error.message : String(error)}\n`);
    return 2;
  }
  await serveUntilStopped(io);
  await session.close();
  return 0;
}
