#!/usr/bin/env bun
import { config as dotenv } from "dotenv";
import { existsSync } from "fs";
import { rm } from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
import { $ } from "bun";
import { buildConfig } from "./helpers";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const envPath = path.resolve(__dirname, "../../.env");
dotenv({ path: envPath });

if (process.argv.includes("--help") || process.argv.includes("-h")) {
  console.log(`
🏗️  Runtime Build Script

Usage: bun run scripts/build/runtime.ts [options]

Builds pertento-runtime to src/apps/runtime/dist

Options:
  --help, -h               Show this help message
  --core                   Build only runtime-core (default)
  --experiments            Build only runtime-experiments
  --tracking               Build only runtime-tracking
  --editor-setup           Build only editor-setup
  --all                    Build all runtimes

Example:
  bun run scripts/build/runtime.ts
  bun run scripts/build/runtime.ts --experiments
  bun run scripts/build/runtime.ts --tracking
  bun run scripts/build/runtime.ts --editor-setup
  bun run scripts/build/runtime.ts --all
`);
  process.exit(0);
}

const formatFileSize = (bytes: number): string => {
  const units = ["B", "KB", "MB", "GB"];
  let size = bytes;
  let unitIndex = 0;

  while (size >= 1024 && unitIndex < units.length - 1) {
    size /= 1024;
    unitIndex++;
  }

  return `${size.toFixed(2)} ${units[unitIndex]}`;
};

interface RuntimeBuildConfig {
  name: string;
  srcDir: string;
  configPath: string;
  outFilename: string;
  format: "iife" | "esm";
  entry?: string;
}

const runtimeConfigs: Record<string, RuntimeBuildConfig> = {
  core: {
    name: "runtime-core",
    srcDir: "src/apps/runtime/runtime-core/src",
    configPath: "src/apps/runtime/runtime-core/src/config.ts",
    outFilename: "pertentoRuntime5.js",
    format: "iife",
  },
  experiments: {
    name: "runtime-experiments",
    srcDir: "src/apps/runtime/runtime-experiments/src",
    configPath: "src/apps/runtime/runtime-experiments/src/config.ts",
    outFilename: "pertentoRuntime5Experiments.js",
    format: "esm",
  },
  tracking: {
    name: "runtime-tracking",
    srcDir: "src/apps/runtime/runtime-tracking/src",
    configPath: "src/apps/runtime/runtime-tracking/src/config.ts",
    outFilename: "pertentoRuntime5Tracking.js",
    format: "iife",
  },
  editorSetup: {
    name: "editor-setup",
    srcDir: "src/apps/runtime/runtime-tracking/src",
    configPath: "src/apps/runtime/runtime-tracking/src/config.ts",
    outFilename: "pertentoEditorSetup.js",
    format: "iife",
    entry: "setup-for-editor-extension.ts",
  },
};

let buildTargets: string[] = ["core"];
if (process.argv.includes("--experiments")) {
  buildTargets = ["experiments"];
} else if (process.argv.includes("--tracking")) {
  buildTargets = ["tracking"];
} else if (process.argv.includes("--editor-setup")) {
  buildTargets = ["editorSetup"];
} else if (process.argv.includes("--all")) {
  buildTargets = ["core", "experiments", "tracking", "editorSetup"];
}

console.log("\n🚀 Starting runtime build process...\n");

const { BUILD_ENV } = process.env;
const isProduction = BUILD_ENV === "production";
console.log(
  `📋 Build environment: ${BUILD_ENV || "development"} ${isProduction ? "(minified)" : "(development)"}\n`,
);

const outdir = "src/apps/runtime/dist";

if (existsSync(outdir)) {
  console.log(`🗑️ Cleaning previous build at ${outdir}`);
  await rm(outdir, { recursive: true, force: true });
}

const start = performance.now();

async function buildRuntime(target: string) {
  const config = runtimeConfigs[target];
  if (!config) {
    console.error(`Unknown runtime target: ${target}`);
    process.exit(1);
  }

  // Generate config with environment values
  console.log(`🔧 Generating config for ${config.name}...\n`);
  buildConfig(config.configPath, target);

  console.log(`📦 Building ${config.name}...\n`);

  const entryFile = config.entry || "index.ts";
  const result = await Bun.build({
    entrypoints: [`${config.srcDir}/${entryFile}`],
    outdir,
    minify: isProduction,
    target: "browser",
    format: config.format,
    naming: config.outFilename,
    define: {
      "process.env.NODE_ENV": JSON.stringify(
        isProduction ? "production" : "development",
      ),
    },
  });

  if (!result.success) {
    console.error(`Build failed for ${config.name}!`);
    process.exit(1);
  }

  // Compress the output files using Bun shell
  const jsFile = path.join(outdir, config.outFilename);
  if (existsSync(jsFile)) {
    // Create gzip version
    await $`gzip -k -f ${jsFile}`;

    // Create brotli version
    await $`brotli -k -f ${jsFile}`;
  }

  return result;
}

// Build all targets
let allOutputs: { path: string; kind: string }[] = [];
for (const target of buildTargets) {
  const result = await buildRuntime(target);
  allOutputs = allOutputs.concat(result.outputs);
}

const end = performance.now();

// Collect all compressed files
const allCompressed = buildTargets.flatMap((target) => {
  const config = runtimeConfigs[target];
  if (!config) return [];
  return [
    { path: path.join(outdir, `${config.outFilename}.gz`), kind: "compressed" },
    { path: path.join(outdir, `${config.outFilename}.br`), kind: "compressed" },
  ];
});

const outputTable = allOutputs
  .concat(allCompressed)
  .filter((output) => existsSync(output.path))
  .map((output) => ({
    File: path.relative(process.cwd(), output.path),
    Size: formatFileSize(Bun.file(output.path).size),
  }));

console.table(outputTable);
const buildTime = (end - start).toFixed(2);

// Copy all output files to runtime-scripts directory
const runtimeScriptsDir = "runtime-scripts";
if (!existsSync(runtimeScriptsDir)) {
  await $`mkdir -p ${runtimeScriptsDir}`;
}

console.log(`\n📁 Copying files to ${runtimeScriptsDir}...`);
for (const output of allOutputs.concat(allCompressed)) {
  if (existsSync(output.path)) {
    const filename = path.basename(output.path);
    await $`cp ${output.path} ${runtimeScriptsDir}/${filename}`;
  }
}

console.log(`\n✅ Runtime build completed in ${buildTime}ms\n`);
