import { readdir, rm, writeFile } from "node:fs/promises";
import { join, relative, sep } from "node:path";
import { fileURLToPath } from "node:url";

const projectRoot = fileURLToPath(new URL("..", import.meta.url));
const distWebDir = join(projectRoot, "dist/web");
const buildDir = join(projectRoot, ".build");
const executablePath = join(projectRoot, "dist/dial-server");

async function build() {
  try {
    await viteBuild();
    await codeGeneration();
    await bunCompile();
    await cleanup();
    console.log(`Built executable: ${executablePath}`);
  } catch (error) {
    await cleanup();
    console.error("Build failed:", error);
    process.exit(1);
  }
}

async function bunCompile() {
  console.log("Step 3/3: Bun compile...");
  await rm(executablePath, { force: true });

  const target = process.env["BUN_TARGET"] ?? process.env["BUILD_TARGET"];
  const result = await Bun.build({
    compile: target
      ? {
          autoloadBunfig: true,
          autoloadDotenv: true,
          outfile: executablePath,
          target: target as Bun.Build.CompileTarget,
        }
      : {
          autoloadBunfig: true,
          autoloadDotenv: true,
          outfile: executablePath,
        },
    entrypoints: [join(buildDir, "server-entry.ts")],
    minify: true,
    sourcemap: "linked",
  });

  if (!result.success) {
    console.error("Bun compile failed:", result.logs);
    await cleanup();
    process.exit(1);
  }
}

async function cleanup() {
  await rm(buildDir, { force: true, recursive: true });
}

async function codeGeneration() {
  console.log("Step 2/3: Code generation...");
  await rm(buildDir, { force: true, recursive: true });
  await Bun.write(join(buildDir, ".gitkeep"), "");

  const allFiles = await scanDir(distWebDir, "/");
  const importLines: string[] = [];
  const fileEntries: string[] = [];
  let indexHtmlVar = "";

  for (let i = 0; i < allFiles.length; i++) {
    const urlPath = allFiles[i]!;
    const varName = `f${i}`;
    const filePath = toImportSpecifier(buildDir, join(distWebDir, urlPath.slice(1)));
    importLines.push(`import ${varName} from "./${filePath}" with { type: "file" };`);

    if (urlPath === "/index.html") {
      indexHtmlVar = varName;
    } else {
      fileEntries.push(`  "${urlPath}": Bun.file(${varName}),`);
    }
  }

  if (!indexHtmlVar) {
    console.error("index.html not found in dist/web/");
    process.exit(1);
  }

  const staticAssetsTs = [
    `import type { StaticAssets } from "../src/server/static";`,
    "",
    ...importLines,
    "",
    `export const staticAssets: StaticAssets = {`,
    `  files: {`,
    ...fileEntries,
    `  },`,
    `  indexHtml: Bun.file(${indexHtmlVar}),`,
    `};`,
    "",
  ].join("\n");

  await writeFile(join(buildDir, "static-assets.ts"), staticAssetsTs);

  const serverEntryTs = [
    `import { bootstrap } from "../src/server/bootstrap";`,
    `import { readRuntimeConfig } from "../src/server/config";`,
    `import { staticAssets } from "./static-assets";`,
    "",
    `async function main() {`,
    `  const { configPath } = readRuntimeConfig();`,
    `  await bootstrap({ configPath, mode: "production", staticAssets });`,
    `}`,
    "",
    `void main().catch((error) => {`,
    `  console.error("启动失败:", error instanceof Error ? error.message : error);`,
    `  process.exit(1);`,
    `});`,
    "",
  ].join("\n");

  await writeFile(join(buildDir, "server-entry.ts"), serverEntryTs);
}

async function scanDir(dir: string, prefix: string): Promise<string[]> {
  const entries = await readdir(dir, { withFileTypes: true });
  const paths: string[] = [];
  for (const entry of entries) {
    const fullPath = join(dir, entry.name);
    const urlPath = `${prefix}${entry.name}`;
    if (entry.isDirectory()) {
      paths.push(...(await scanDir(fullPath, `${urlPath}/`)));
    } else {
      paths.push(urlPath);
    }
  }
  return paths;
}

function toImportSpecifier(fromDir: string, targetPath: string) {
  return relative(fromDir, targetPath).split(sep).join("/");
}

async function viteBuild() {
  console.log("Step 1/3: Vite build...");
  const proc = Bun.spawn(["bunx", "--bun", "vite", "build"], {
    cwd: projectRoot,
    stderr: "inherit",
    stdout: "inherit",
  });
  const exitCode = await proc.exited;
  if (exitCode !== 0) {
    console.error("Vite build failed");
    process.exit(1);
  }
}

await build();
