import type * as __ManduMandujsCoreTypes0 from "@mandujs/core";
/**
 * mandu start - Production server
 *
 * Production version of dev.ts without dev-only features (HMR, file watching, Guard).
 * Must be run after mandu build.
 */
import {
  startServer,
  formatServerAddresses,
  loadEnv,
  validateAndReport,
  runHook,
  type RoutesManifest,
} from "@mandujs/core";
import type { BundleManifest } from "@mandujs/core/bundler";
import { newId } from "@mandujs/core/id";
import { resolveFromCwd } from "../util/fs";
import { CLI_ERROR_CODES, printCLIError } from "../errors";
import { resolveManifest } from "../util/manifest";
import { resolveAvailablePort } from "../util/port";
import {
  validateRuntimeLockfile,
  handleBlockedLockfile,
  printRuntimeLockfileStatus,
} from "../util/lockfile";
import { registerManifestHandlers } from "@mandujs/core";
import { removeRuntimeControl, writeRuntimeControl } from "../util/runtime-control";
import { resolveDisplayHost } from "../util/host";
import { createCachedBundledImporter } from "../util/bun";
import path from "path";
import fs from "fs";

export interface StartOptions {
  port?: number;
}

export async function start(options: StartOptions = {}): Promise<void> {
  const rootDir = resolveFromCwd(".");
  const config = await validateAndReport(rootDir);

  if (!config) {
    printCLIError(CLI_ERROR_CODES.CONFIG_VALIDATION_FAILED);
    process.exit(1);
  }

  // Check build artifacts
  const manifestJsonPath = path.join(rootDir, ".mandu/manifest.json");
  if (!fs.existsSync(manifestJsonPath)) {
    console.error("❌ No build artifacts found. Run 'mandu build' first.");
    process.exit(1);
  }

  // Load bundle manifest
  let bundleManifest: BundleManifest | undefined;
  try {
    const raw = fs.readFileSync(manifestJsonPath, "utf-8");
    bundleManifest = JSON.parse(raw);
  } catch {
    console.warn("⚠️  Failed to parse bundle manifest. Island hydration will be disabled.");
  }

  // #211 — guard against stale / dev / empty manifest. `mandu start` is
  // a production command; serving a dev manifest silently produces
  // unstyled pages because CSS/JS referenced in the HTML was never
  // emitted by the build. Symmetric with the strict lockfile gate.
  if (bundleManifest) {
    const env = bundleManifest.env;
    if (env && env !== "production") {
      console.error(
        `❌ Stale manifest detected (env=${env}). 'mandu start' requires a production build.\n` +
        `   Run 'mandu build' first, then retry.`
      );
      process.exit(1);
    }
    const bundleCount = bundleManifest.bundles
      ? Object.keys(bundleManifest.bundles).length
      : 0;
    if (bundleCount === 0 && env !== "production") {
      // Empty bundles on a non-production manifest — definitely not a real build.
      console.error(
        `❌ Empty manifest (bundles: {}). 'mandu start' would serve unstyled pages.\n` +
        `   Run 'mandu build' first, then retry.`
      );
      process.exit(1);
    }
  }

  // Lockfile validation (strict: block policy)
  const { lockfile, lockResult, action, bypassed } = await validateRuntimeLockfile(config, rootDir);
  handleBlockedLockfile(action, lockResult);

  const serverConfig = config.server ?? {};
  const plugins = config.plugins ?? [];
  const configHooks = config.hooks;
  const managementToken = newId();

  console.log(`🥟 Mandu Production Server`);

  // Print lockfile status
  printRuntimeLockfileStatus(action, bypassed, lockfile, lockResult);

  // Load .env files (production mode)
  const envResult = await loadEnv({
    rootDir,
    env: "production",
  });

  if (envResult.loaded.length > 0) {
    console.log(`🔐 Env loaded: ${envResult.loaded.join(", ")}`);
  }

  // Scan routes
  console.log(`📂 Scanning routes...`);
  let manifest: RoutesManifest;

  try {
    const resolved = await resolveManifest(rootDir, { fsRoutes: config.fsRoutes });
    manifest = resolved.manifest;

    if (manifest.routes.length === 0) {
      printCLIError(CLI_ERROR_CODES.DEV_NO_ROUTES);
      process.exit(1);
    }

    console.log(`✅ ${manifest.routes.length} route(s) found\n`);
  } catch (error) {
    printCLIError(CLI_ERROR_CODES.DEV_MANIFEST_NOT_FOUND);
    console.error(error instanceof Error ? error.message : error);
    process.exit(1);
  }

  // Register handlers. Boundary-transformed routes need the SSR bundler's
  // onLoad transform so relative imports keep resolving from the original file.
  const registeredLayouts = new Set<string>();
  const hasClientBoundaries = manifest.routes.some((route) => route.kind === "page" && !!route.boundaries?.length);
  const boundaryImporter = hasClientBoundaries ? createCachedBundledImporter({ rootDir }) : null;
  const productionImport = async (
    modulePath: string,
    opts?: {
      changedFile?: string;
      clientBoundaryTransform?: {
        routeId: string;
        hydrate?: string;
        boundaries?: Array<{
          id?: string;
          ordinal: number;
          source?: {
            file: string;
          };
        }>;
      };
    },
  ) => {
    if (boundaryImporter && opts?.clientBoundaryTransform) {
      return boundaryImporter(modulePath, opts);
    }

    const url = Bun.pathToFileURL(modulePath);
    return import(url.href);
  };

  await registerManifestHandlers(manifest, rootDir, {
    importFn: productionImport,
    registeredLayouts,
  });
  console.log("");

  // Determine port
  const envPort = process.env.PORT ? Number(process.env.PORT) : undefined;
  const desiredPort =
    options.port ??
    (envPort && Number.isFinite(envPort) ? envPort : undefined) ??
    serverConfig.port ??
    3333;

  // Port is explicitly configured if it came from CLI flag, env var, or config file
  const isExplicitPort = !!(
    options.port ||
    (envPort && Number.isFinite(envPort)) ||
    serverConfig.port
  );

  let port: number;
  try {
    const resolved = await resolveAvailablePort(desiredPort, {
      hostname: serverConfig.hostname,
      offsets: [0],
      strict: isExplicitPort,
    });
    port = resolved.port;
  } catch (error) {
    if (isExplicitPort) {
      printCLIError(CLI_ERROR_CODES.DEV_PORT_IN_USE, { port: desiredPort });
      process.exit(1);
    }
    throw error;
  }

  if (port !== desiredPort) {
    console.warn(`⚠️  Port ${desiredPort} is in use. Using ${port} instead.`);
  }

  await runHook("onBeforeStart", plugins, configHooks);

  // Determine CSS path (inject when built CSS file exists)
  const cssFilePath = path.join(rootDir, ".mandu", "client", "globals.css");
  const hasCss = fs.existsSync(cssFilePath);
  const cssPath: string | false = hasCss ? "/.mandu/client/globals.css" : false;

  // Start main server (production mode)
  // Adapter가 설정되어 있으면 어댑터 사용, 없으면 기본 Bun 서버
  const serverOptions = {
    port,
    hostname: serverConfig.hostname,
    rootDir,
    isDev: false,
    bundleManifest,
    cors: serverConfig.cors,
    streaming: serverConfig.streaming,
    rateLimit: serverConfig.rateLimit,
    cssPath,
    cache: true,
    managementToken,
    // Issue #192 — smooth navigation primitives honored in prod
    transitions: config.transitions,
    prefetch: config.prefetch,
    // Issue #208 — inline SPA-nav helper honored in prod. Default `true`
    // via `ssr.spa` Zod schema; `false` suppresses the IIFE + reverts
    // the client router to opt-in (`data-mandu-link`).
    spa: config.spa,
  };

  let actualPort: number;
  let stopFn: () => void;

  const adapter = (config as Record<string, unknown>).adapter as __ManduMandujsCoreTypes0.ManduAdapter | undefined;

  if (adapter) {
    console.log(`🔌 Using adapter: ${adapter.name}`);
    const adapterServer = adapter.createServer({
      manifest,
      bundleManifest,
      rootDir,
      serverOptions,
    });
    const address = await adapterServer.listen(port, serverConfig.hostname);
    actualPort = address.port;
    stopFn = () => { void adapterServer.close(); };
  } else {
    const server = startServer(manifest, serverOptions);
    actualPort = server.server.port ?? port;
    stopFn = () => { server.stop(); };
  }

  // Translate wildcard bind addresses to a browser-navigable loopback URL. (#190)
  const displayHost = resolveDisplayHost(serverConfig.hostname);
  const baseUrl = `http://${displayHost}:${actualPort}`;
  console.log(`\n🚀 Production server running on ${baseUrl}`);
  // #225 — compute the "also reachable at" list from the actual bind
  // address. `formatServerAddresses()` guarantees every URL it reports
  // is answered by the socket we just started (no more lying about
  // `[::1]` when bound to `0.0.0.0`).
  const { additional } = formatServerAddresses(serverConfig.hostname, actualPort);
  if (additional.length > 0) {
    console.log(`   (also reachable at ${additional.join(", ")})`);
  }

  await writeRuntimeControl(rootDir, {
    mode: "start",
    port: actualPort,
    token: managementToken,
    baseUrl,
    startedAt: new Date().toISOString(),
  });

  // Graceful shutdown
  const cleanup = () => {
    console.log("\n🛑 Shutting down server...");
    stopFn();
    void removeRuntimeControl(rootDir).finally(() => {
      process.exit(0);
    });
  };

  process.on("SIGINT", cleanup);
  process.on("SIGTERM", cleanup);
}
