/**
 * `wrnexus dev` — the development supervisor.
 *
 * The child server process owns file watching and HMR now (see @wrnexus/dev-server):
 *  - CSS and client-island edits update the live page over a WebSocket with no
 *    process restart and no full reload.
 *  - When a server module changes (it can't be re-imported in-process), the
 *    child exits with RESTART_EXIT_CODE and this supervisor respawns it. The
 *    browser reconnects and morphs in the new HTML — no visible refresh.
 *
 * The supervisor therefore only (re)launches the child; it does not watch files.
 */

import { spawn, type ChildProcess } from "node:child_process";
import { resolve, join } from "node:path";
import { existsSync, watch, type FSWatcher } from "node:fs";
import { fileURLToPath } from "node:url";
import { RESTART_EXIT_CODE } from "@wrnexus/dev-server";

// Resolve the dev server child entry through the package (not a source path) so
// it works whether @wrnexus/dev-server is a workspace or an installed dependency.
const SERVE_ENTRY = fileURLToPath(import.meta.resolve("@wrnexus/dev-server/serve-entry"));

export function runDev(
  appRoot: string,
  port: number,
  hostname = "::",
  tls?: { certFile: string; keyFile: string },
): void {
  const appDir = join(resolve(appRoot), "app");
  let child: ChildProcess | null = null;
  let shuttingDown = false;

  const spawnChild = (): void => {
    child = spawn(
      process.execPath, // the Bun binary
      [
        SERVE_ENTRY,
        appDir,
        String(port),
        "development",
        hostname,
        "true",
        ...(tls ? [tls.certFile, tls.keyFile] : []),
      ],
      { stdio: "inherit" },
    );

    child.on("exit", (code, signal) => {
      if (shuttingDown || signal) return;
      if (code === RESTART_EXIT_CODE) {
        spawnChild(); // requested restart — respawn immediately
        return;
      }
      if (code && code !== 0) {
        // Crash (e.g. a syntax error). Respawn after a short delay so the
        // watcher comes back and the server auto-recovers once it's fixed.
        console.error(`[wrnexus] server exited (code ${code}); retrying in 1.2s…`);
        setTimeout(() => {
          if (!shuttingDown) spawnChild();
        }, 1200);
      }
    });
  };

  console.log(`\n  ⚡ WrNexus dev (HMR) — ${appDir}`);

  // Regenerate typed DB queries + typed routes once before starting, then launch.
  // (Best effort; rerun `wrnexus db generate` after editing .sql.)
  void (async () => {
    try {
      const { loadAppConfig } = await import("@wrnexus/styles");
      const { regenerateAllQueries } = await import("./db.ts");
      const config = await loadAppConfig(resolve(appRoot));
      await regenerateAllQueries(appDir, config); // default + every named database
      console.log("  ↻ db queries generated");
    } catch {
      /* non-fatal */
    }
    try {
      const { regenerateRoutes } = await import("./routes.ts");
      const n = regenerateRoutes(appDir);
      console.log(`  ↻ ${n} typed routes generated`);
    } catch {
      /* non-fatal */
    }
    spawnChild();
  })();

  const shutdown = () => {
    shuttingDown = true;
    child?.kill();
    process.exit(0);
  };
  process.on("SIGINT", shutdown);
  process.on("SIGTERM", shutdown);
}

export async function runProductionDev(
  appRoot: string,
  port: number,
  hostname = "::",
): Promise<void> {
  const root = resolve(appRoot);
  let child: ChildProcess | undefined;
  let building = false;
  let pending = false;
  let timer: ReturnType<typeof setTimeout> | undefined;
  const { runBuild } = await import("./build.ts");

  const rebuild = async (): Promise<void> => {
    if (building) {
      pending = true;
      return;
    }
    building = true;
    try {
      await runBuild(root);
      child?.kill();
      const { runPreview } = await import("./preview.ts");
      child = runPreview(root, { port, hostname, developmentRuntime: true });
    } catch (error) {
      console.error(
        "[wrnexus] production-runtime rebuild failed; keeping the last good server.",
        error,
      );
    } finally {
      building = false;
      if (pending) {
        pending = false;
        void rebuild();
      }
    }
  };

  console.log(`\n  ⚡ WrNexus dev (exact production runtime) — ${root}`);
  await rebuild();
  const watchers: FSWatcher[] = [];
  for (const name of [
    "app",
    "public",
    "wrnexus.config.ts",
    "wrnexus.config.js",
    "wrnexus.config.mjs",
  ]) {
    const target = join(root, name);
    if (!existsSync(target)) continue;
    watchers.push(
      watch(target, { recursive: true }, () => {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => void rebuild(), 120);
      }),
    );
  }
  const shutdown = (): void => {
    watchers.forEach((watcher) => watcher.close());
    child?.kill();
    process.exit(0);
  };
  process.on("SIGINT", shutdown);
  process.on("SIGTERM", shutdown);
}
