#!/usr/bin/env node
// Bundle the @bynk/runtime modules into the single flat file that bynk-emit
// embeds via include_str! and emits verbatim as `out/runtime.ts`.
//
// The emitted runtime is, by contract, ONE file with a flat public surface:
// every emitted module imports named symbols from a relative `./runtime.js`.
// So this is a deterministic concatenation, not a tree-shaking bundler — the
// whole runtime always ships. We strip the modules' internal relative imports
// (their symbols all coexist in one module scope after concatenation) and keep
// every comment, so the emitted file stays human-readable.
//
//   node scripts/bundle.mjs           # (re)write the artifact
//   node scripts/bundle.mjs --check   # fail if the artifact is out of date
//
// `--check` is the drift guard: it rebuilds in memory and diffs against the
// committed file, so CI catches a stale runtime.ts without needing a Node step
// inside `cargo build`.

import { readFile, writeFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";

const here = dirname(fileURLToPath(import.meta.url));
const pkgRoot = join(here, "..");
const SRC = join(pkgRoot, "src");
// The single file bynk-emit's `include_str!("emitter/runtime.ts")` consumes.
const TARGET = join(pkgRoot, "..", "src", "emitter", "runtime.ts");

// Concatenation order: a topological sort of the module dependency graph, so a
// symbol is declared before the modules that reference it. Must mirror the
// re-export order in src/index.ts.
const MODULES = [
  "result",
  "bytes",
  "errors",
  "storage",
  "boundary",
  "http",
  "queue",
  "agent",
  "auth",
  "connection",
];

const HEADER = [
  "// Generated by bynkc — do not edit by hand.",
  "// bynk runtime — shared by every emitted module.",
].join("\n");

// Drop a module's internal cross-imports (e.g. `import { Ok } from "./result.ts";`).
// After concatenation every symbol lives in one module scope, so these imports
// are not only redundant but invalid. Only relative ("./") specifiers are
// stripped; the runtime has no external dependencies, so nothing else should
// appear.
const INTERNAL_IMPORT = /^import\s[\s\S]*?\sfrom\s+["']\.\/[^"']+["'];\s*$/;

function stripInternalImports(source) {
  return source
    .split("\n")
    .filter((line) => !INTERNAL_IMPORT.test(line))
    .join("\n")
    .trim();
}

async function build() {
  const bodies = [];
  for (const name of MODULES) {
    const raw = await readFile(join(SRC, `${name}.ts`), "utf8");
    const body = stripInternalImports(raw);
    if (body.length > 0) bodies.push(body);
  }
  return `${HEADER}\n\n${bodies.join("\n\n")}\n`;
}

const check = process.argv.includes("--check");
const bundled = await build();

if (check) {
  let current = null;
  try {
    current = await readFile(TARGET, "utf8");
  } catch {
    /* missing target counts as drift */
  }
  if (current !== bundled) {
    console.error(
      "runtime.ts is out of date with the @bynk/runtime sources.\n" +
        "Run `npm run bundle` in bynk-emit/runtime and commit src/emitter/runtime.ts.",
    );
    process.exit(1);
  }
  console.log("runtime.ts is up to date.");
} else {
  await writeFile(TARGET, bundled, "utf8");
  console.log(`Wrote ${TARGET} (${bundled.length} bytes).`);
}
