/**
 * Shared helpers for edge-target emitters (Workers / Deno / Vercel / Netlify).
 *
 * Every edge target follows the same pattern:
 *   1. Emit `manifest.json`, `register.ts`, `<adapter>.entry.ts` under
 *      `.mandu/<target>/`
 *   2. Bundle the entry with `safeBuild`
 *   3. Write or preserve the target's config file (wrangler.toml / deno.json
 *      / vercel.json / netlify.toml)
 *
 * This file centralizes the pieces that are identical across all four
 * targets — register-source generation and project-name inference — so the
 * per-target emitters stay focused on their unique bundling + config emit
 * logic.
 */

import path from "path";
import fs from "fs/promises";
import type { RoutesManifest } from "@mandujs/core";

export interface GenerateRegisterOptions {
  /** The edge target label, used in the "Auto-generated by" header. */
  targetLabel: string;
  /** Absolute path to the project root. */
  rootDir: string;
  /** Absolute path to the directory the register.ts is being written into. */
  outputDir: string;
}

/**
 * Generate `register.ts`. Imports every route module in the manifest using
 * Bun.build-resolvable paths. Each route module's import side-effect
 * registers its handler through the module-level `registerApiHandler` /
 * `registerPageHandler` calls populated by `@mandujs/cli`'s runtime.
 *
 * For FS-Routes projects the route modules live at `app/**`; their files
 * export `ManduFilling` instances that we wrap at runtime. In the emitted
 * bundle we eagerly import them so the module graph is tree-shaken into
 * the final output.
 */
export function generateRegisterSource(
  manifest: RoutesManifest,
  options: GenerateRegisterOptions
): string {
  const { targetLabel, rootDir, outputDir } = options;

  const lines: string[] = [];
  lines.push(`// Auto-generated by mandu build --target=${targetLabel}.`);
  lines.push(`// Do not edit — re-runs overwrite this file.`);
  lines.push(`import {`);
  lines.push(`  registerApiHandler,`);
  lines.push(`  registerPageHandler,`);
  lines.push(`  registerPageLoader,`);
  lines.push(`  registerLayoutLoader,`);
  lines.push(`  needsHydration,`);
  lines.push(`} from "@mandujs/core";`);
  lines.push("");

  let importIndex = 0;
  const imports: string[] = [];
  const registrations: string[] = [];

  for (const route of manifest.routes) {
    if (route.kind === "api") {
      if (!route.module) continue;
      const importName = `route_${importIndex++}`;
      const relPath = toRelativeImport(outputDir, rootDir, route.module);
      imports.push(`import * as ${importName} from "${relPath}";`);
      registrations.push(
        [
          `registerApiHandler(${JSON.stringify(route.id)}, async (req: Request, params: Record<string, string> = {}) => {`,
          `  const mod: any = ${importName};`,
          `  const target: any = mod.default ?? mod.handler ?? mod;`,
          `  if (target && typeof target.handle === "function") {`,
          `    return target.handle(req, params);`,
          `  }`,
          `  if (target && typeof target === "object") {`,
          `    const method = req.method.toUpperCase();`,
          `    const fn = target[method];`,
          `    if (typeof fn === "function") {`,
          `      return fn(req, { params });`,
          `    }`,
          `  }`,
          `  return new Response("Method Not Allowed", { status: 405 });`,
          `});`,
        ].join("\n")
      );
    } else if (route.kind === "page" && route.componentModule) {
      const importName = `page_${importIndex++}`;
      const relPath = toRelativeImport(outputDir, rootDir, route.componentModule);
      imports.push(`import * as ${importName} from "${relPath}";`);
      if (route.slotModule) {
        registrations.push(
          [
            `registerPageHandler(${JSON.stringify(route.id)}, async () => {`,
            `  const mod: any = ${importName};`,
            `  const rawDefault = mod.default;`,
            `  if (typeof rawDefault === "function") {`,
            `    return { component: rawDefault, filling: mod.filling };`,
            `  }`,
            `  if (typeof rawDefault === "object" && rawDefault !== null) {`,
            `    return { ...rawDefault };`,
            `  }`,
            `  throw new Error("[Mandu] Page module '${route.id}' has no default export.");`,
            `});`,
          ].join("\n")
        );
      } else {
        registrations.push(
          `registerPageLoader(${JSON.stringify(route.id)}, async () => ${importName});`
        );
      }
      if (Array.isArray(route.layoutChain)) {
        for (const layoutPath of route.layoutChain) {
          const layoutImportName = `layout_${importIndex++}`;
          const layoutRelPath = toRelativeImport(outputDir, rootDir, layoutPath);
          imports.push(`import * as ${layoutImportName} from "${layoutRelPath}";`);
          registrations.push(
            `registerLayoutLoader(${JSON.stringify(layoutPath)}, async () => ${layoutImportName});`
          );
        }
      }
    }
  }

  lines.push(...imports);
  lines.push("");
  lines.push("// eslint-disable-next-line @typescript-eslint/no-unused-vars");
  lines.push("const _needsHydration = needsHydration; // keep symbol referenced");
  lines.push("");
  lines.push(...registrations);
  lines.push("");
  lines.push(`export {};`);

  return lines.join("\n");
}

/**
 * Derive a kebab-case project name from `package.json` suitable for edge
 * platform naming constraints (lowercase, alphanumeric + hyphens, max 63
 * characters). Returns `undefined` when `package.json` is missing or its
 * `name` field is not a string.
 */
export async function inferProjectName(rootDir: string): Promise<string | undefined> {
  try {
    const pkgRaw = await fs.readFile(path.join(rootDir, "package.json"), "utf-8");
    const pkg = JSON.parse(pkgRaw) as { name?: unknown };
    if (typeof pkg.name !== "string") return undefined;
    return pkg.name
      .toLowerCase()
      .replace(/@[^/]+\//, "")
      .replace(/[^a-z0-9-]+/g, "-")
      .replace(/^-+|-+$/g, "")
      .slice(0, 63);
  } catch {
    return undefined;
  }
}

/**
 * Canonicalize an absolute or project-relative module path into a
 * bundler-resolvable relative import from `outputDir`. Uses POSIX
 * separators so Bun.build / Deno / Vercel / Netlify resolvers handle it
 * uniformly on Windows.
 */
export function toRelativeImport(
  outputDir: string,
  rootDir: string,
  modulePath: string
): string {
  const abs = path.isAbsolute(modulePath) ? modulePath : path.join(rootDir, modulePath);
  let rel = path.relative(outputDir, abs);
  rel = rel.replace(/\\/g, "/");
  if (!rel.startsWith(".")) rel = `./${rel}`;
  return rel;
}
