/**
 * Deno Deploy bundle emitter.
 *
 * Runs as the last step of `mandu build --target=deno`. Produces:
 *
 *   .mandu/deno/server.ts    — bundled Deno.serve entry
 *   .mandu/deno/register.ts  — SSR handler registration wiring
 *   .mandu/deno/manifest.json — cloned RoutesManifest (serializable copy)
 *   deno.json                — generated if absent; preserved otherwise
 *
 * Deno Deploy consumes TypeScript directly so we do NOT run `Bun.build`
 * here — we emit a plain `server.ts` entry that the Deno runtime picks up
 * via `deno task dev` / `deployctl deploy`. The `safeBuild` path remains
 * an opt-in for users who want a pre-compiled JS bundle (`--compile`).
 */

import path from "path";
import fs from "fs/promises";
import { existsSync } from "fs";
import type { RoutesManifest } from "@mandujs/core";
import { generateDenoConfig } from "@mandujs/edge/deno";
import { generateRegisterSource, inferProjectName } from "./edge-shared";

const DENO_OUTPUT_DIR = ".mandu/deno";
const DENO_ENTRY_FILENAME = "server.ts";
const DENO_REGISTER_FILENAME = "register.ts";
const DENO_MANIFEST_FILENAME = "manifest.json";
const DENO_CONFIG_FILENAME = "deno.json";

export interface EmitDenoBundleOptions {
  /** Absolute path to the project root. */
  rootDir: string;
  /** Parsed routes manifest — same object the main build consumed. */
  manifest: RoutesManifest;
  /** CSS link path (from the main build). Forwarded to the Deno handler. */
  cssPath: string | false;
  /** Override the deno project name. Defaults to a slug of the package name. */
  projectName?: string;
}

export interface EmitDenoBundleResult {
  /** Absolute path to the generated server.ts. */
  serverEntryPath: string;
  /** Whether deno.json was newly generated (vs preserved). */
  denoConfigGenerated: boolean;
  /** Absolute path to deno.json. */
  denoConfigPath: string;
}

/**
 * Emit the Deno Deploy deployment artifacts. Unlike Workers, Deno
 * consumes TypeScript directly so we emit source files rather than a
 * single bundle.
 */
export async function emitDenoBundle(
  options: EmitDenoBundleOptions
): Promise<EmitDenoBundleResult> {
  const { rootDir, manifest, cssPath, projectName: explicitName } = options;

  console.log(`\n🦕 Building Deno Deploy bundle...`);

  const outputDir = path.join(rootDir, DENO_OUTPUT_DIR);
  await fs.mkdir(outputDir, { recursive: true });

  // 1. Manifest copy for the server to import at runtime.
  const manifestPath = path.join(outputDir, DENO_MANIFEST_FILENAME);
  await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));

  // 2. register.ts imports every route module so the module side effects
  //    populate the Mandu registries before the first request.
  const registerSource = generateRegisterSource(manifest, {
    targetLabel: "deno",
    rootDir,
    outputDir,
  });
  const registerPath = path.join(outputDir, DENO_REGISTER_FILENAME);
  await fs.writeFile(registerPath, registerSource);

  // 3. Deno server entry. Thin wrapper that calls `Deno.serve(handler)`.
  const entrySource = generateDenoEntrySource({
    manifestRelativePath: `./${DENO_MANIFEST_FILENAME}`,
    registerRelativePath: `./${DENO_REGISTER_FILENAME}`,
    cssPath,
  });
  const entryPath = path.join(outputDir, DENO_ENTRY_FILENAME);
  await fs.writeFile(entryPath, entrySource);

  // 4. Emit or preserve deno.json.
  const projectName =
    (explicitName ?? (await inferProjectName(rootDir))) || "mandu-deno";
  const denoConfigPath = path.join(rootDir, DENO_CONFIG_FILENAME);
  const denoConfigExists = existsSync(denoConfigPath);

  if (!denoConfigExists) {
    const json = generateDenoConfig({
      projectName,
      entry: `${DENO_OUTPUT_DIR}/${DENO_ENTRY_FILENAME}`.replace(/\\/g, "/"),
    });
    await fs.writeFile(denoConfigPath, json);
  }

  console.log(`   ✅ server.ts (${DENO_OUTPUT_DIR}/${DENO_ENTRY_FILENAME})`);
  console.log(`   📄 deno.json ${denoConfigExists ? "(preserved)" : "(generated)"}`);
  console.log(`   Output: ${DENO_OUTPUT_DIR}/`);
  console.log(`\n   Next: deno task dev   or   deno task deploy`);

  return {
    serverEntryPath: entryPath,
    denoConfigGenerated: !denoConfigExists,
    denoConfigPath,
  };
}

/**
 * Generate `server.ts` — the Deno.serve entry that `deno run` / `deployctl`
 * pick up. Deno consumes TypeScript natively so no pre-bundling is needed.
 */
function generateDenoEntrySource(opts: {
  manifestRelativePath: string;
  registerRelativePath: string;
  cssPath: string | false;
}): string {
  const cssPathExpr =
    opts.cssPath === false ? "false" : JSON.stringify(opts.cssPath);
  return [
    `// Auto-generated by mandu build --target=deno.`,
    `// Deno.serve entry for Deno Deploy.`,
    `import manifest from "${opts.manifestRelativePath}" with { type: "json" };`,
    `import { createDenoHandler } from "@mandujs/edge/deno";`,
    `import "${opts.registerRelativePath}";`,
    ``,
    `const fetch = createDenoHandler(manifest as any, {`,
    `  cssPath: ${cssPathExpr},`,
    `});`,
    ``,
    `// Deno-typed global guard so this module is still buildable under Bun.`,
    `declare const Deno: { serve(handler: (req: Request, info?: unknown) => Promise<Response>): unknown };`,
    `if (typeof Deno !== "undefined" && typeof (Deno as { serve?: unknown }).serve === "function") {`,
    `  Deno.serve(fetch as any);`,
    `}`,
    ``,
    `export default fetch;`,
    ``,
  ].join("\n");
}
