import { spawn } from "node:child_process";
import { createHash } from "node:crypto";
import {
  access,
  constants as fsConstants,
  copyFile,
  lstat,
  mkdir,
  mkdtemp,
  readFile,
  readdir,
  realpath,
  rename,
  rm,
  stat,
  writeFile,
} from "node:fs/promises";
import path from "node:path";

export const RUST_LOCK = Object.freeze({
  bridgeAbi: 1,
  engineVersion: "1.13.1",
  rustcVersion: "1.97.1",
  rustcIdentity: "rustc 1.97.1 (8bab26f4f 2026-07-14)",
  rustcCommit: "8bab26f4f68e0e26f0bb7960be334d5b520ea452",
  cargoIdentity: "cargo 1.97.1 (c980f4866 2026-06-30)",
  llvmVersion: "22.1.6",
  wasmBindgenVersion: "0.2.126",
  wasmBindgenCrateChecksum:
    "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4",
  wasmBindgenCliChecksum:
    "8e2ebf6eef87c34e662347f1dddfbea541304bb7294711ed2c2acd8746cc5b50",
  sourceDateEpoch: 1_784_146_753,
  source: Object.freeze({
    repository: "https://github.com/rust-lang/regex",
    tag: "1.13.1",
    tagObject: "7c28ee4dc4aef53571c96757f4825c51ac0c3d44",
    commit: "2b527599eb9eea0dcc288c704584f242f26a5c61",
    tree: "25bea9f6f55a4cf528afefaa00f5335c595c182c",
    crateChecksum:
      "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d",
    license: "MIT OR Apache-2.0",
  }),
  distribution: Object.freeze({
    manifest: "https://static.rust-lang.org/dist/channel-rust-1.97.1.toml",
    manifestSha256:
      "03569b1886ceb5c05276b50c8431ab111de944cd6140fe1fa7d821dd8e0f29cf",
    rustcArchiveSha256:
      "1c441e430c1cca49dff54a8d59c41038bf6f79f7b8756596cb2f36511a015eba",
    wasmStdArchiveSha256:
      "13902d5573eeea50701d75acc774b6df2dfb4942ec88cdbe40bb07e448c307ea",
    licenseHashes: Object.freeze({
      apache:
        "074e6e32c86a4c0ef8b3ed25b721ca23aca83df277cd88106ef7177c354615ff",
      mit: "b85dcd3e453d05982552c52b5fc9e0bdd6d23c6f8e844b984a88af32570b0cc0",
      unicode:
        "f5062c9a188d81dfe66b56db4182dcf9e4b17c0d9b0d311a8e20b3a1b075c443",
      stdlibCopyright:
        "0a65bb747c49c7bb816cbc7188319bd6e4e8d08091c1190b8a3c0971c47968ed",
    }),
  }),
  limits: Object.freeze({
    maximumPatternBytes: 262_144,
    maximumSubjectBytes: 16_777_216,
    maximumReplacementBytes: 262_144,
    maximumMatches: 10_000,
    maximumCaptureRows: 100_000,
    maximumCaptureGroups: 1_000,
    maximumOutputBytes: 67_108_864,
    maximumRequestJSONBytes: 103_874_560,
    compiledSizeLimitBytes: 10_485_760,
    dfaSizeLimitBytes: 2_097_152,
  }),
});

const PACK_FILES = Object.freeze([
  "LICENSE-Apache-2.0.txt",
  "LICENSE-MIT.txt",
  "LICENSE-Unicode-3.0.txt",
  "RUST-STDLIB-COPYRIGHT.html",
  "SHA256SUMS",
  "THIRD_PARTY_LICENSES.txt",
  "engine-metadata.json",
  "rust-regex.mjs",
  "rust-regex_bg.wasm",
]);
const CHECKSUM_FILES = Object.freeze(
  PACK_FILES.filter((name) => name !== "SHA256SUMS"),
);
const SOURCE_FILES = Object.freeze([
  "engines/rust/Cargo.lock",
  "engines/rust/Cargo.toml",
  "engines/rust/README.md",
  "engines/rust/rust-toolchain.toml",
  "engines/rust/src/lib.rs",
]);

function sha256(value) {
  return createHash("sha256").update(value).digest("hex");
}

async function sha256File(file) {
  return sha256(await readFile(file));
}

async function assertRealDirectory(candidate, label) {
  const details = await lstat(candidate).catch(() => null);
  if (!details?.isDirectory() || details.isSymbolicLink()) {
    throw new Error(`${label} must be a real directory: ${candidate}`);
  }
  return realpath(candidate);
}

async function assertRegularFile(candidate, label) {
  const details = await lstat(candidate).catch(() => null);
  if (!details?.isFile() || details.isSymbolicLink()) {
    throw new Error(`${label} must be a regular file: ${candidate}`);
  }
  return realpath(candidate);
}

function commandLabel(command, arguments_) {
  return [command, ...arguments_]
    .map((part) =>
      /^[A-Za-z0-9_./:=,+-]+$/u.test(part) ? part : JSON.stringify(part),
    )
    .join(" ");
}

async function run(command, arguments_, options = {}) {
  const child = spawn(command, arguments_, {
    cwd: options.cwd,
    env: options.env,
    stdio: ["ignore", "pipe", "pipe"],
  });
  const stdout = [];
  const stderr = [];
  child.stdout.on("data", (chunk) => stdout.push(chunk));
  child.stderr.on("data", (chunk) => stderr.push(chunk));
  const result = await new Promise((resolve, reject) => {
    child.once("error", reject);
    child.once("close", (code, signal) => resolve({ code, signal }));
  });
  const output = Buffer.concat(stdout).toString("utf8");
  const errorOutput = Buffer.concat(stderr).toString("utf8");
  if (result.code !== 0) {
    const detail = [output, errorOutput].filter(Boolean).join("\n").trim();
    throw new Error(
      `${commandLabel(command, arguments_)} failed with ${
        result.signal ? `signal ${result.signal}` : `exit code ${result.code}`
      }${detail ? `:\n${detail}` : "."}`,
    );
  }
  return { stdout: output, stderr: errorOutput };
}

function parseCargoLock(contents) {
  const packages = [];
  for (const block of contents.split("[[package]]").slice(1)) {
    const field = (name) => {
      const match = block.match(new RegExp(`^${name} = "([^"]+)"`, "mu"));
      return match?.[1];
    };
    const name = field("name");
    const version = field("version");
    if (!name || !version) {
      throw new Error("Cargo.lock contains a malformed package block.");
    }
    const source = field("source");
    const checksum = field("checksum");
    packages.push({
      name,
      version,
      ...(source ? { source } : {}),
      ...(checksum ? { checksum } : {}),
    });
  }
  return packages;
}

async function findRegistryCrate(cargoHome, package_) {
  const registrySource = path.join(cargoHome, "registry", "src");
  const indexes = await readdir(registrySource, { withFileTypes: true });
  const matches = [];
  for (const index of indexes) {
    if (!index.isDirectory() || index.isSymbolicLink()) continue;
    const candidate = path.join(
      registrySource,
      index.name,
      `${package_.name}-${package_.version}`,
    );
    const details = await lstat(candidate).catch(() => null);
    if (details?.isDirectory() && !details.isSymbolicLink()) {
      matches.push(await realpath(candidate));
    }
  }
  if (matches.length !== 1) {
    throw new Error(
      `Expected one cached source directory for ${package_.name} ${package_.version}.`,
    );
  }
  return matches[0];
}

async function thirdPartyLicenses(cargoHome, packages) {
  const sections = [
    "Regex Tools Rust engine third-party source notices",
    "Generated from the checked-in Cargo.lock; package order is deterministic.",
    "",
  ];
  for (const package_ of packages.filter(({ source }) => source)) {
    const crate = await findRegistryCrate(cargoHome, package_);
    const entries = (await readdir(crate, { withFileTypes: true }))
      .filter(
        (entry) =>
          entry.isFile() &&
          !entry.isSymbolicLink() &&
          /^(?:COPYING|COPYRIGHT|LICENSE|UNLICENSE)/iu.test(entry.name),
      )
      .sort((left, right) =>
        left.name < right.name ? -1 : left.name > right.name ? 1 : 0,
      );
    sections.push(
      `===============================================================================`,
      `${package_.name} ${package_.version}`,
      `Cargo source: ${package_.source}`,
      `Cargo checksum: ${package_.checksum ?? "not recorded"}`,
    );
    if (entries.length === 0) {
      sections.push(
        "No root licence file was present in the packaged crate source.",
        "",
      );
      continue;
    }
    for (const entry of entries) {
      sections.push(
        `-------------------------------------------------------------------------------`,
        entry.name,
        "",
        (await readFile(path.join(crate, entry.name), "utf8")).trimEnd(),
        "",
      );
    }
  }
  return `${sections.join("\n")}\n`;
}

async function verifyToolchain(toolchainRootDirectory) {
  const root = await assertRealDirectory(
    toolchainRootDirectory,
    "Rust 1.97.1 toolchain",
  );
  const cargoHome = await assertRealDirectory(
    path.join(root, "cargo"),
    "Rust Cargo home",
  );
  const rustupHome = await assertRealDirectory(
    path.join(root, "rustup"),
    "Rustup home",
  );
  const toolchain = await assertRealDirectory(
    path.join(rustupHome, "toolchains", "1.97.1-x86_64-unknown-linux-gnu"),
    "Rust distribution toolchain",
  );
  const cargo = await assertRegularFile(
    path.join(toolchain, "bin", "cargo"),
    "cargo",
  );
  const rustc = await assertRegularFile(
    path.join(toolchain, "bin", "rustc"),
    "rustc",
  );
  const wasmBindgen = await assertRegularFile(
    path.join(cargoHome, "bin", "wasm-bindgen"),
    "wasm-bindgen",
  );
  for (const executable of [cargo, rustc, wasmBindgen]) {
    await access(executable, fsConstants.X_OK);
  }
  const baseEnvironment = {
    ...process.env,
    CARGO_HOME: cargoHome,
    RUSTUP_HOME: rustupHome,
    PATH: [
      path.join(toolchain, "bin"),
      path.join(cargoHome, "bin"),
      process.env.PATH ?? "",
    ].join(path.delimiter),
  };
  const rustcVersion = await run(rustc, ["--version", "--verbose"], {
    env: baseEnvironment,
  });
  if (
    !rustcVersion.stdout.includes(`release: ${RUST_LOCK.rustcVersion}`) ||
    !rustcVersion.stdout.includes(`commit-hash: ${RUST_LOCK.rustcCommit}`) ||
    !rustcVersion.stdout.includes(`LLVM version: ${RUST_LOCK.llvmVersion}`)
  ) {
    throw new Error("The Rust engine requires pinned rustc 1.97.1.");
  }
  const cargoVersion = await run(cargo, ["--version"], {
    env: baseEnvironment,
  });
  if (cargoVersion.stdout.trim() !== RUST_LOCK.cargoIdentity) {
    throw new Error("The Rust engine requires pinned Cargo 1.97.1.");
  }
  const bindgenVersion = await run(wasmBindgen, ["--version"], {
    env: baseEnvironment,
  });
  if (
    bindgenVersion.stdout.trim() !==
    `wasm-bindgen ${RUST_LOCK.wasmBindgenVersion}`
  ) {
    throw new Error("The Rust engine requires pinned wasm-bindgen 0.2.126.");
  }

  const docs = path.join(toolchain, "share", "doc", "rust");
  const files = {
    apache: await assertRegularFile(
      path.join(docs, "licenses", "Apache-2.0.txt"),
      "Rust Apache licence",
    ),
    mit: await assertRegularFile(
      path.join(docs, "licenses", "MIT.txt"),
      "Rust MIT licence",
    ),
    unicode: await assertRegularFile(
      path.join(docs, "licenses", "Unicode-3.0.txt"),
      "Rust Unicode licence",
    ),
    stdlibCopyright: await assertRegularFile(
      path.join(docs, "COPYRIGHT-library.html"),
      "Rust standard-library copyright notice",
    ),
  };
  for (const [name, file] of Object.entries(files)) {
    if (
      (await sha256File(file)) !== RUST_LOCK.distribution.licenseHashes[name]
    ) {
      throw new Error(`The pinned Rust ${name} notice does not match 1.97.1.`);
    }
  }
  return {
    root,
    cargoHome,
    rustupHome,
    cargo,
    rustc,
    wasmBindgen,
    baseEnvironment,
    files,
  };
}

function buildEnvironment(toolchain, repositoryRoot, targetDirectory) {
  const environment = { ...toolchain.baseEnvironment };
  for (const variable of [
    "CARGO_BUILD_RUSTC",
    "CARGO_ENCODED_RUSTFLAGS",
    "CARGO_PROFILE_RELEASE_DEBUG",
    "RUSTC",
    "RUSTDOC",
    "RUSTDOCFLAGS",
    "RUSTFLAGS",
  ]) {
    delete environment[variable];
  }
  environment.CARGO_INCREMENTAL = "0";
  environment.CARGO_NET_OFFLINE = "true";
  environment.CARGO_TARGET_DIR = targetDirectory;
  environment.LANG = "C";
  environment.LC_ALL = "C";
  environment.REGEX_TOOLS_RUSTC_VERSION = RUST_LOCK.rustcIdentity;
  environment.RUST_BACKTRACE = "0";
  environment.RUSTUP_TOOLCHAIN = "1.97.1-x86_64-unknown-linux-gnu";
  environment.RUSTFLAGS = [
    `--remap-path-prefix=${repositoryRoot}=.`,
    `--remap-path-prefix=${toolchain.cargoHome}=./cargo-home`,
  ].join(" ");
  environment.SOURCE_DATE_EPOCH = String(RUST_LOCK.sourceDateEpoch);
  environment.TZ = "UTC";
  return environment;
}

async function sourceMetadata(root) {
  return Promise.all(
    SOURCE_FILES.map(async (relativePath) => {
      const contents = await readFile(path.join(root, relativePath));
      return {
        path: relativePath,
        sha256: sha256(contents),
        bytes: contents.byteLength,
      };
    }),
  );
}

async function fileMetadata(pack, name) {
  const contents = await readFile(path.join(pack, name));
  return { path: name, sha256: sha256(contents), bytes: contents.byteLength };
}

async function lockedPackages(root) {
  const contents = await readFile(
    path.join(root, "engines", "rust", "Cargo.lock"),
    "utf8",
  );
  return parseCargoLock(contents)
    .filter(({ source }) => source)
    .map(({ name, version, checksum }) => ({ name, version, checksum }));
}

async function expectedMetadata(root, pack) {
  return {
    schemaVersion: 1,
    engine: "rust",
    engineName: "Rust regex crate",
    engineVersion: RUST_LOCK.engineVersion,
    semanticIdentity:
      "Rust regex crate 1.13.1 compiled to WebAssembly with rustc 1.97.1",
    status: "production-native-runtime",
    bridge: {
      abiVersion: RUST_LOCK.bridgeAbi,
      offsetUnit: "utf8-byte",
      requestEncoding:
        "Exact UTF-8-bounded JSON values with worst-case escape allowance; fixed exports; base64 UTF-8 replacement output; no source evaluation",
      supportedApplicationFlags: ["g", "i", "m", "s", "U", "u", "x", "R"],
      unicode:
        "Enabled by default, matching the Rust regex crate; u explicitly affirms that default.",
      replacement:
        "Bounded streaming expansion with regex::Captures::expand grammar, parity-checked against the native API.",
      limits: { ...RUST_LOCK.limits },
      sourceFiles: await sourceMetadata(root),
    },
    source: {
      repository: RUST_LOCK.source.repository,
      tag: RUST_LOCK.source.tag,
      tagObjectSha1: RUST_LOCK.source.tagObject,
      commitSha1: RUST_LOCK.source.commit,
      treeSha1: RUST_LOCK.source.tree,
      crateChecksumSha256: RUST_LOCK.source.crateChecksum,
      license: RUST_LOCK.source.license,
    },
    toolchain: {
      rustcVersion: RUST_LOCK.rustcVersion,
      rustcIdentity: RUST_LOCK.rustcIdentity,
      rustcCommitSha1: RUST_LOCK.rustcCommit,
      llvmVersion: RUST_LOCK.llvmVersion,
      cargoIdentity: RUST_LOCK.cargoIdentity,
      target: "wasm32-unknown-unknown",
      wasmBindgenVersion: RUST_LOCK.wasmBindgenVersion,
      wasmBindgenCrateChecksumSha256: RUST_LOCK.wasmBindgenCrateChecksum,
      wasmBindgenCliChecksumSha256: RUST_LOCK.wasmBindgenCliChecksum,
      distributionManifest: RUST_LOCK.distribution.manifest,
      distributionManifestSha256: RUST_LOCK.distribution.manifestSha256,
      rustcArchiveSha256: RUST_LOCK.distribution.rustcArchiveSha256,
      wasmStdArchiveSha256: RUST_LOCK.distribution.wasmStdArchiveSha256,
      cargoLockPackages: await lockedPackages(root),
    },
    sourceDateEpoch: RUST_LOCK.sourceDateEpoch,
    files: await Promise.all(
      CHECKSUM_FILES.filter((name) => name !== "engine-metadata.json").map(
        (name) => fileMetadata(pack, name),
      ),
    ),
  };
}

async function checksumText(pack) {
  const lines = await Promise.all(
    CHECKSUM_FILES.map(
      async (name) => `${await sha256File(path.join(pack, name))}  ${name}`,
    ),
  );
  return `${lines.join("\n")}\n`;
}

function equalJson(left, right) {
  return JSON.stringify(left) === JSON.stringify(right);
}

async function smokePack(pack) {
  const moduleUrl = new URL(`file://${path.join(pack, "rust-regex.mjs")}`).href;
  const wasmPath = path.join(pack, "rust-regex_bg.wasm");
  const smoke = `
    import { readFile } from "node:fs/promises";
    const engine = await import(process.argv[1]);
    engine.initSync({
      module: await WebAssembly.compile(await readFile(process.argv[2])),
    });
    const identity = JSON.parse(engine.identity());
    if (
      identity.abi !== 1 ||
      identity.ok !== true ||
      identity.identity.engineVersion !== "1.13.1" ||
      identity.identity.rustcVersion !==
        "rustc 1.97.1 (8bab26f4f 2026-07-14)" ||
      identity.identity.selfTest !== true
    ) throw new Error("Rust identity self-test failed.");
    const result = JSON.parse(engine.run(JSON.stringify({
      abi: 1,
      operation: "replace",
      pattern: "(?P<word>😀+)",
      flags: "g",
      options: {},
      subject: "x😀😀",
      scanAll: false,
      maximumMatches: 10,
      maximumCaptureRows: 10,
      replacement: "\${word}-$$",
      maximumOutputBytes: 100,
    })));
    if (
      result.ok !== true ||
      result.matches[0].span[0] !== 1 ||
      result.matches[0].span[1] !== 9 ||
      Buffer.from(result.outputBase64, "base64").toString("utf8") !== "x😀😀-$"
    ) throw new Error("Rust execution self-test failed.");
  `;
  await run(process.execPath, [
    "--input-type=module",
    "--eval",
    smoke,
    moduleUrl,
    wasmPath,
  ]);
}

export async function verifyRustPack(packDirectory, root) {
  const repositoryRoot = await assertRealDirectory(
    root,
    "Regex Tools repository",
  );
  const pack = await assertRealDirectory(packDirectory, "Rust engine pack");
  const entries = (await readdir(pack, { withFileTypes: true })).sort(
    (left, right) =>
      left.name < right.name ? -1 : left.name > right.name ? 1 : 0,
  );
  if (
    entries.length !== PACK_FILES.length ||
    entries.some(
      (entry, index) =>
        entry.name !== PACK_FILES[index] ||
        !entry.isFile() ||
        entry.isSymbolicLink(),
    )
  ) {
    throw new Error(
      `The Rust pack must contain only: ${PACK_FILES.join(", ")}.`,
    );
  }
  const licenseFiles = {
    "LICENSE-Apache-2.0.txt": RUST_LOCK.distribution.licenseHashes.apache,
    "LICENSE-MIT.txt": RUST_LOCK.distribution.licenseHashes.mit,
    "LICENSE-Unicode-3.0.txt": RUST_LOCK.distribution.licenseHashes.unicode,
    "RUST-STDLIB-COPYRIGHT.html":
      RUST_LOCK.distribution.licenseHashes.stdlibCopyright,
  };
  for (const [name, digest] of Object.entries(licenseFiles)) {
    if ((await sha256File(path.join(pack, name))) !== digest) {
      throw new Error(`${name} does not match the Rust 1.97.1 distribution.`);
    }
  }
  const wasm = await readFile(path.join(pack, "rust-regex_bg.wasm"));
  if (
    wasm.byteLength < 100_000 ||
    wasm.subarray(0, 4).toString("hex") !== "0061736d"
  ) {
    throw new Error("The staged Rust WebAssembly module is invalid.");
  }
  const moduleText = await readFile(path.join(pack, "rust-regex.mjs"), "utf8");
  if (
    moduleText.includes("sourceMappingURL") ||
    moduleText.includes("/mnt/") ||
    moduleText.includes("/home/") ||
    !moduleText.includes("export function identity") ||
    !moduleText.includes("export function run") ||
    !moduleText.includes("export { initSync")
  ) {
    throw new Error(
      "rust-regex.mjs leaks a build path, has a source map, or lacks bridge exports.",
    );
  }
  const packages = await lockedPackages(repositoryRoot);
  const notices = await readFile(
    path.join(pack, "THIRD_PARTY_LICENSES.txt"),
    "utf8",
  );
  for (const package_ of packages) {
    if (
      !notices.includes(`${package_.name} ${package_.version}\n`) ||
      !notices.includes(`Cargo checksum: ${package_.checksum}`)
    ) {
      throw new Error(
        `Third-party notices omit ${package_.name} ${package_.version}.`,
      );
    }
  }

  let metadata;
  try {
    metadata = JSON.parse(
      await readFile(path.join(pack, "engine-metadata.json"), "utf8"),
    );
  } catch (error) {
    throw new Error("Rust engine-metadata.json is invalid JSON.", {
      cause: error,
    });
  }
  if (!equalJson(metadata, await expectedMetadata(repositoryRoot, pack))) {
    throw new Error(
      "The Rust metadata does not match the pinned build contract.",
    );
  }
  if (
    (await readFile(path.join(pack, "SHA256SUMS"), "utf8")) !==
    (await checksumText(pack))
  ) {
    throw new Error("The Rust SHA256SUMS file is incorrect.");
  }
  await smokePack(pack);
  return metadata;
}

async function replaceDirectory(stage, target, label) {
  const details = await lstat(target).catch(() => null);
  if (details && (!details.isDirectory() || details.isSymbolicLink())) {
    throw new Error(`${label} must be a real directory.`);
  }
  if (!details) {
    await rename(stage, target);
    return;
  }
  const backup = `${target}.replaced-${process.pid}`;
  await rename(target, backup);
  try {
    await rename(stage, target);
  } catch (error) {
    await rename(backup, target);
    throw error;
  }
  await rm(backup, { recursive: true });
}

async function bindgen(toolchain, rawWasm, output) {
  await mkdir(output, { recursive: true });
  await run(
    toolchain.wasmBindgen,
    [
      "--target",
      "web",
      "--no-typescript",
      "--out-name",
      "rust-regex",
      "--out-dir",
      output,
      rawWasm,
    ],
    { env: toolchain.baseEnvironment },
  );
  await rename(
    path.join(output, "rust-regex.js"),
    path.join(output, "rust-regex.mjs"),
  );
}

export async function buildRustPack(
  root,
  toolchainRootDirectory,
  outputDirectory,
) {
  const repositoryRoot = await assertRealDirectory(
    root,
    "Regex Tools repository",
  );
  const engine = await assertRealDirectory(
    path.join(repositoryRoot, "engines", "rust"),
    "Rust engine source",
  );
  const toolchain = await verifyToolchain(toolchainRootDirectory);
  const lockPackages = parseCargoLock(
    await readFile(path.join(engine, "Cargo.lock"), "utf8"),
  );
  const regexPackage = lockPackages.find(
    ({ name, version }) => name === "regex" && version === "1.13.1",
  );
  const bindgenPackage = lockPackages.find(
    ({ name, version }) =>
      name === "wasm-bindgen" && version === RUST_LOCK.wasmBindgenVersion,
  );
  if (
    regexPackage?.checksum !== RUST_LOCK.source.crateChecksum ||
    bindgenPackage?.checksum !== RUST_LOCK.wasmBindgenCrateChecksum
  ) {
    throw new Error("Cargo.lock does not contain the pinned engine crates.");
  }

  const buildParent = path.join(repositoryRoot, ".engine-build");
  await mkdir(buildParent, { recursive: true });
  const temporary = await mkdtemp(path.join(buildParent, "rust-build-"));
  try {
    const generated = [];
    for (const label of ["first", "second"]) {
      const target = path.join(temporary, `${label}-target`);
      const environment = buildEnvironment(toolchain, repositoryRoot, target);
      await run(
        toolchain.cargo,
        [
          "build",
          "--locked",
          "--offline",
          "--release",
          "--target",
          "wasm32-unknown-unknown",
        ],
        { cwd: engine, env: environment },
      );
      const rawWasm = await assertRegularFile(
        path.join(
          target,
          "wasm32-unknown-unknown",
          "release",
          "regex_tools_rust_engine.wasm",
        ),
        "compiled Rust WebAssembly",
      );
      const output = path.join(temporary, `${label}-bindgen`);
      await bindgen(toolchain, rawWasm, output);
      generated.push(output);
    }
    for (const name of ["rust-regex.mjs", "rust-regex_bg.wasm"]) {
      if (
        (await sha256File(path.join(generated[0], name))) !==
        (await sha256File(path.join(generated[1], name)))
      ) {
        throw new Error(
          `Two pinned Rust builds produced different ${name} hashes.`,
        );
      }
    }

    const target = path.resolve(
      repositoryRoot,
      outputDirectory ?? path.join(".engine-build", "rust"),
    );
    const stage = await mkdtemp(path.join(buildParent, "rust-pack-"));
    await copyFile(
      toolchain.files.apache,
      path.join(stage, "LICENSE-Apache-2.0.txt"),
    );
    await copyFile(toolchain.files.mit, path.join(stage, "LICENSE-MIT.txt"));
    await copyFile(
      toolchain.files.unicode,
      path.join(stage, "LICENSE-Unicode-3.0.txt"),
    );
    await copyFile(
      toolchain.files.stdlibCopyright,
      path.join(stage, "RUST-STDLIB-COPYRIGHT.html"),
    );
    await copyFile(
      path.join(generated[0], "rust-regex.mjs"),
      path.join(stage, "rust-regex.mjs"),
    );
    await copyFile(
      path.join(generated[0], "rust-regex_bg.wasm"),
      path.join(stage, "rust-regex_bg.wasm"),
    );
    await writeFile(
      path.join(stage, "THIRD_PARTY_LICENSES.txt"),
      await thirdPartyLicenses(toolchain.cargoHome, lockPackages),
      "utf8",
    );
    const metadata = await expectedMetadata(repositoryRoot, stage);
    await writeFile(
      path.join(stage, "engine-metadata.json"),
      `${JSON.stringify(metadata, null, 2)}\n`,
      "utf8",
    );
    await writeFile(path.join(stage, "SHA256SUMS"), await checksumText(stage));
    await verifyRustPack(stage, repositoryRoot);
    await mkdir(path.dirname(target), { recursive: true });
    await replaceDirectory(stage, target, "Rust staged pack");
    return { metadata, output: target };
  } finally {
    await rm(temporary, { recursive: true });
  }
}

export async function installRustPack(sourceDirectory, root) {
  const repositoryRoot = await assertRealDirectory(
    root,
    "Regex Tools repository",
  );
  const source = await assertRealDirectory(sourceDirectory, "Rust staged pack");
  const metadata = await verifyRustPack(source, repositoryRoot);
  const engineParent = path.join(repositoryRoot, "public", "engines");
  await mkdir(engineParent, { recursive: true });
  const stage = await mkdtemp(path.join(engineParent, "rust-install-"));
  for (const name of PACK_FILES) {
    await copyFile(path.join(source, name), path.join(stage, name));
  }
  await verifyRustPack(stage, repositoryRoot);
  const output = path.join(engineParent, "rust");
  await replaceDirectory(stage, output, "Installed Rust engine pack");
  return { metadata, output };
}
