import { createHash } from "node:crypto";
import { createServer } from "node:http";
import {
  constants as fsConstants,
  copyFile,
  lstat,
  mkdir,
  mkdtemp,
  readFile,
  readdir,
  realpath,
  rename,
  rm,
  writeFile,
} from "node:fs/promises";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { unzipSync } from "fflate";
import {
  verifyChecksummedEnginePack,
  writeEngineChecksums,
} from "./checksummed-engine-pack.mjs";

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");

export const PERL_ENGINE_LOCK = Object.freeze({
  bridgeAbi: 1,
  engineVersion: "5.28.1",
  perlVersion: "v5.28.1",
  webPerlVersion: "0.09-beta",
  sourceDateEpoch: 1_551_644_196,
  source: Object.freeze({
    repository: "https://github.com/haukex/webperl",
    tag: "v0.09-beta",
    tagObject: "c5933d11666906328b238d03459707207b6ca2b4",
    commit: "6f2173d29a2c2e3536e1de75ff5d291ae96ab348",
    tree: "20e59a142ad2e355fc3ed03da974bdf183b79c66",
    license: "GPL-1.0-or-later OR Artistic-1.0-Perl",
  }),
  prebuilt: Object.freeze({
    root: "webperl_prebuilt_v0.09-beta",
    archive:
      "https://github.com/haukex/webperl/releases/download/v0.09-beta/webperl_prebuilt_v0.09-beta.zip",
    archiveSha256:
      "5f441249217e90ab378c666f473d4206ab4f44907f6bb0aa8d70834bc38c40dc",
    archiveBytes: 3_936_557,
    files: Object.freeze({
      "emperl.data": Object.freeze({
        upstream: "emperl.data",
        sha256:
          "9529019418cf766a42cf2d25bd3fc97b47c9e689f5666cfc32dc11338d1b1e66",
        bytes: 12_021_691,
      }),
      "emperl.js": Object.freeze({
        upstream: "emperl.js",
        sha256:
          "b60e3c04874c6ef5278001257b4c8a9f4c7e69ca3d6b268d9639723234844784",
        bytes: 303_013,
      }),
      "emperl.wasm": Object.freeze({
        upstream: "emperl.wasm",
        sha256:
          "f1d49c4514c7332a57992c4a2444fd6a56ae3b5e6651b4fd484852a641e5e4ec",
        bytes: 3_734_063,
      }),
      "LICENSE-WebPerl-Artistic.txt": Object.freeze({
        upstream: "LICENSE_artistic.txt",
        sha256:
          "435a788b2de9aaae9c640e7c0ab7b317b41323522e8641bcda000607892abf1f",
        bytes: 7_587,
      }),
      "LICENSE-WebPerl-GPL.txt": Object.freeze({
        upstream: "LICENSE_gpl.txt",
        sha256:
          "0166b8b8e96a6b153da26ece157ebbd9ffad5423670f611fae80fefacb6da7f5",
        bytes: 14_468,
      }),
    }),
  }),
  supportingFiles: Object.freeze({
    "LICENSE-Devel-StackTrace-Artistic-2.0.txt": Object.freeze({
      source: "engines/perl/LICENSE-Devel-StackTrace-Artistic-2.0.txt",
      sha256:
        "e16dd93533bb65e25fad00d06e88840d9b5fd6bb80d551d8866126c52d89e1de",
      bytes: 9_048,
    }),
    "LICENSE-Emscripten.txt": Object.freeze({
      source: "engines/perl/LICENSE-Emscripten.txt",
      sha256:
        "51aea7641f81d560eb039bc97ce35e2517e2656fe8731eb49ce6a18498eb22fe",
      bytes: 5_092,
    }),
    "NOTICE.txt": Object.freeze({
      source: "engines/perl/NOTICE.txt",
    }),
    "perl-runtime.worker.js": Object.freeze({
      source: "engines/perl/perl-runtime.worker.js",
    }),
    "regex_tools_bridge.pl": Object.freeze({
      source: "engines/perl/regex_tools_bridge.pl",
    }),
  }),
  develStackTrace: Object.freeze({
    version: "2.03",
    sourceArchive:
      "https://www.cpan.org/authors/id/D/DR/DROLSKY/Devel-StackTrace-2.03.tar.gz",
    sourceArchiveSha256:
      "7618cd4ebe24e254c17085f4b418784ab503cb4cb3baf8f48a7be894e59ba848",
  }),
  toolchain: Object.freeze({
    emscriptenVersion: "1.38.28",
    target: "wasm32-unknown-emscripten",
    license: "MIT OR NCSA",
  }),
  limits: Object.freeze({
    maximumPatternCharacters: 65_536,
    maximumSubjectBytes: 16_777_216,
    maximumReplacementCharacters: 65_536,
    maximumMatches: 10_000,
    maximumCaptureRows: 100_000,
    maximumCaptureGroups: 1_000,
    maximumOutputBytes: 67_108_864,
    maximumErrorCharacters: 1_024,
    maximumRequestJsonBytes: 101_466_112,
    maximumResponseJsonBytes: 8_388_608,
    outputChunkCharacters: 4_096,
  }),
});

const SOURCE_FILES = Object.freeze([
  "engines/perl/README.md",
  "engines/perl/perl-runtime.worker.js",
  "engines/perl/regex_tools_bridge.pl",
]);
const PACK_FILES = Object.freeze(
  [
    ...Object.keys(PERL_ENGINE_LOCK.prebuilt.files),
    ...Object.keys(PERL_ENGINE_LOCK.supportingFiles),
    "SHA256SUMS",
    "engine-metadata.json",
  ].sort(),
);
const CHECKSUM_FILES = Object.freeze(
  PACK_FILES.filter((name) => name !== "SHA256SUMS"),
);
const CONTENT_FILES = Object.freeze(
  PACK_FILES.filter(
    (name) => name !== "SHA256SUMS" && name !== "engine-metadata.json",
  ),
);

function compareText(left, right) {
  return left < right ? -1 : left > right ? 1 : 0;
}

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, non-symlink file: ${candidate}`,
    );
  }
  return realpath(candidate);
}

function assertSafeOutputTarget(target, repository) {
  const filesystemRoot = path.parse(target).root;
  const repositoryFromTarget = path.relative(target, repository);
  const targetContainsRepository =
    repositoryFromTarget === "" ||
    (!repositoryFromTarget.startsWith("..") &&
      !path.isAbsolute(repositoryFromTarget));
  if (target === filesystemRoot || targetContainsRepository) {
    throw new Error(
      "Perl pack output may not be a filesystem root or contain the repository.",
    );
  }
}

function assertBytes(value, descriptor, label) {
  const actualHash = sha256(value);
  if (
    value.byteLength !== descriptor.bytes ||
    actualHash !== descriptor.sha256
  ) {
    throw new Error(
      `${label} does not match the pinned WebPerl release: expected ${descriptor.bytes} bytes and ${descriptor.sha256}, got ${value.byteLength} bytes and ${actualHash}.`,
    );
  }
}

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

async function fileRecord(directory, name) {
  const value = await readFile(path.join(directory, name));
  return { path: name, sha256: sha256(value), bytes: value.byteLength };
}

function dynamicExecutionAudit(loader, worker, harness) {
  const loaderEvalSites = loader.match(/\beval\s*\(/gu)?.length ?? 0;
  const loaderFunctionSites = loader.match(/\bnew\s+Function\b/gu)?.length ?? 0;
  if (
    loaderEvalSites !== 1 ||
    loaderFunctionSites !== 0 ||
    !loader.includes("var rv=eval(code)")
  ) {
    throw new Error(
      "The generated WebPerl loader dynamic-code audit no longer matches the pinned release.",
    );
  }
  if (
    !worker.includes('var FIXED_INVOCATION = "RegexTools::run_request()"') ||
    !worker.includes('var OUTPUT_PATH = "/tmp/regex-tools-output.bin"') ||
    !worker.includes('new TextDecoder("utf-8", { fatal: true })') ||
    !worker.includes("injectReplacementOutput(parsed") ||
    !worker.includes("[FIXED_INVOCATION]") ||
    !worker.includes("importScripts(") ||
    /\beval\s*\(/u.test(worker) ||
    /\bnew\s+Function\b/u.test(worker)
  ) {
    throw new Error(
      "The classic WebPerl worker no longer has a fixed, source-free invocation channel.",
    );
  }
  if (
    !harness.includes("no re 'eval';") ||
    !harness.includes("sub _contains_code_assertion") ||
    !harness.includes("sub _tokenize_replacement") ||
    !harness.includes("sub _resolve_named_spans") ||
    !harness.includes("sub _append_replacement_tokens") ||
    !harness.includes("sub _append_span") ||
    harness.includes("sub _capture_values") ||
    harness.includes("sub _expand_replacement") ||
    harness.includes("$response->{output}") ||
    !harness.includes("code-assertion-disabled") ||
    !harness.includes("use Cpanel::JSON::XS ();") ||
    harness.includes("use WebPerl") ||
    (harness.match(/\beval\s*\{/gu)?.length ?? 0) !== 3 ||
    /\beval\s+(?:\\?["']|\$)/u.test(harness)
  ) {
    throw new Error(
      "The fixed Perl bridge no longer satisfies its no-user-source-evaluation contract.",
    );
  }
  return {
    generatedLoaderJavaScriptEvalSites: loaderEvalSites,
    generatedLoaderNewFunctionSites: loaderFunctionSites,
  };
}

async function verifyRepositorySources(projectRoot) {
  const repository = await assertRealDirectory(
    projectRoot,
    "Regex Tools repository",
  );
  for (const relativePath of SOURCE_FILES) {
    await assertRegularFile(
      path.join(repository, relativePath),
      `Perl source ${relativePath}`,
    );
  }
  for (const [name, descriptor] of Object.entries(
    PERL_ENGINE_LOCK.supportingFiles,
  )) {
    const source = await assertRegularFile(
      path.join(repository, descriptor.source),
      `Perl supporting source ${name}`,
    );
    if (
      descriptor.sha256 &&
      ((await sha256File(source)) !== descriptor.sha256 ||
        (await readFile(source)).byteLength !== descriptor.bytes)
    ) {
      throw new Error(`${name} does not match its pinned upstream licence.`);
    }
  }
  const loader = await readFile(
    path.join(repository, "public", "engines", "perl", "emperl.js"),
    "utf8",
  ).catch(() => "");
  const worker = await readFile(
    path.join(repository, "engines", "perl", "perl-runtime.worker.js"),
    "utf8",
  );
  const harness = await readFile(
    path.join(repository, "engines", "perl", "regex_tools_bridge.pl"),
    "utf8",
  );
  if (loader) dynamicExecutionAudit(loader, worker, harness);
  return repository;
}

async function archiveInputs(archivePath) {
  const archive = await assertRegularFile(
    archivePath,
    "WebPerl v0.09-beta prebuilt archive",
  );
  const encoded = await readFile(archive);
  if (
    encoded.byteLength !== PERL_ENGINE_LOCK.prebuilt.archiveBytes ||
    sha256(encoded) !== PERL_ENGINE_LOCK.prebuilt.archiveSha256
  ) {
    throw new Error(
      "The local WebPerl archive does not match the pinned v0.09-beta prebuilt release.",
    );
  }
  const entries = unzipSync(encoded);
  const result = new Map();
  for (const [output, descriptor] of Object.entries(
    PERL_ENGINE_LOCK.prebuilt.files,
  )) {
    const entry =
      entries[`${PERL_ENGINE_LOCK.prebuilt.root}/${descriptor.upstream}`];
    if (!entry) {
      throw new Error(
        `The pinned WebPerl archive is missing ${descriptor.upstream}.`,
      );
    }
    const value = Buffer.from(entry);
    assertBytes(value, descriptor, `Archived ${descriptor.upstream}`);
    result.set(output, value);
  }
  return result;
}

async function locatePrebuiltDirectory(directory) {
  const candidate = await assertRealDirectory(
    directory,
    "Extracted WebPerl v0.09-beta directory",
  );
  const direct = await lstat(path.join(candidate, "emperl.js")).catch(
    () => null,
  );
  if (direct?.isFile() && !direct.isSymbolicLink()) return candidate;
  return assertRealDirectory(
    path.join(candidate, PERL_ENGINE_LOCK.prebuilt.root),
    "Extracted WebPerl v0.09-beta release root",
  );
}

async function directoryInputs(directory) {
  const prebuilt = await locatePrebuiltDirectory(directory);
  const result = new Map();
  for (const [output, descriptor] of Object.entries(
    PERL_ENGINE_LOCK.prebuilt.files,
  )) {
    const source = await assertRegularFile(
      path.join(prebuilt, descriptor.upstream),
      `Extracted WebPerl ${descriptor.upstream}`,
    );
    const value = await readFile(source);
    assertBytes(value, descriptor, `Extracted ${descriptor.upstream}`);
    result.set(output, value);
  }
  return result;
}

async function expectedMetadata(projectRoot, pack) {
  return {
    schemaVersion: 1,
    engine: "perl",
    engineName: "Perl regular expressions via WebPerl",
    engineVersion: PERL_ENGINE_LOCK.engineVersion,
    semanticIdentity:
      "Perl v5.28.1 regular expressions via the legacy WebPerl v0.09-beta prebuilt runtime",
    status: "legacy-beta-compatibility-runtime",
    bridge: {
      abiVersion: PERL_ENGINE_LOCK.bridgeAbi,
      nativeOffsetUnit: "Unicode code points (Perl character offsets)",
      requestEncoding:
        "exact-key and byte-bounded JSON in a fixed MEMFS request file; no request value is interpolated into source",
      responseEncoding:
        "byte-bounded metadata JSON in a fixed MEMFS response file",
      workerTopology:
        "supervised ES module worker with a nested self-hosted classic WebPerl worker",
      evaluatorInvocation: "RegexTools::run_request()",
      userSourceEvaluation: false,
      regexCodeAssertionsEnabled: false,
      supportedApplicationFlags: [
        "g",
        "i",
        "m",
        "s",
        "x",
        "n",
        "a",
        "d",
        "l",
        "u",
      ],
      replacement:
        "Pre-tokenized fixed grammar supporting literal text, $$, greedy $n, and ${name}; bounded UTF-8 chunks stream from native spans without a complete capture-value array, expansion, or output materialization",
      replacementParityOracle:
        "identity self-test compares fixed named and numbered fixtures with native Perl s/// output",
      replacementOutputTransport:
        "successful UTF-8 output uses a separate fixed MEMFS binary file; the classic worker validates exact length, bound, canonical UTF-8 and injects the decoded string for PortableEngineAdapter",
      captureNames:
        "Perl-native numbered participation and spans; syntax metadata annotates capture-row names; named replacement lookup uses native %+ and resolves the selected value back to a native span before output",
      limits: { ...PERL_ENGINE_LOCK.limits },
      sourceFiles: await sourceRecords(projectRoot),
    },
    securityAudit: {
      generatedLoaderJavaScriptEvalSites: 1,
      generatedLoaderNewFunctionSites: 0,
      evalSitePurpose:
        "optional WebPerl Perl-to-JavaScript interoperability import; not loaded or invoked by regex-tools",
      normalPathCsp: "script-src 'self' 'wasm-unsafe-eval'; no 'unsafe-eval'",
      runtimeRegexEval: "disabled with no re 'eval'",
      codeAssertionDefense:
        "fixed scanner rejects (?{...}) and (??{...}) before compilation",
      killability:
        "the supervised worker tree can be terminated by EngineSupervisor",
      boundedTransport:
        "request and response JSON have exact field/byte contracts; replacement output is not JSON-escaped",
    },
    source: {
      project: "WebPerl",
      repository: PERL_ENGINE_LOCK.source.repository,
      tag: PERL_ENGINE_LOCK.source.tag,
      tagObjectSha1: PERL_ENGINE_LOCK.source.tagObject,
      commitSha1: PERL_ENGINE_LOCK.source.commit,
      treeSha1: PERL_ENGINE_LOCK.source.tree,
      license: PERL_ENGINE_LOCK.source.license,
      perl: {
        version: PERL_ENGINE_LOCK.engineVersion,
        repository: "https://github.com/Perl/perl5",
        tag: "v5.28.1",
        license: PERL_ENGINE_LOCK.source.license,
      },
    },
    prebuilt: {
      archive: PERL_ENGINE_LOCK.prebuilt.archive,
      archiveSha256: PERL_ENGINE_LOCK.prebuilt.archiveSha256,
      archiveBytes: PERL_ENGINE_LOCK.prebuilt.archiveBytes,
      runtimeAssetsCopiedByteForByte: true,
    },
    toolchain: {
      emscriptenVersion: PERL_ENGINE_LOCK.toolchain.emscriptenVersion,
      target: PERL_ENGINE_LOCK.toolchain.target,
      license: PERL_ENGINE_LOCK.toolchain.license,
    },
    bundledModules: [
      {
        name: "Cpanel::JSON::XS",
        version: "4.09",
        license: PERL_ENGINE_LOCK.source.license,
        normalEnginePath: true,
      },
      {
        name: "Devel::StackTrace",
        version: PERL_ENGINE_LOCK.develStackTrace.version,
        license: "Artistic-2.0",
        normalEnginePath: false,
        sourceArchive: PERL_ENGINE_LOCK.develStackTrace.sourceArchive,
        sourceArchiveSha256:
          PERL_ENGINE_LOCK.develStackTrace.sourceArchiveSha256,
      },
      {
        name: "Future",
        version: "0.39",
        license: PERL_ENGINE_LOCK.source.license,
        normalEnginePath: false,
      },
    ],
    sourceDateEpoch: PERL_ENGINE_LOCK.sourceDateEpoch,
    files: await Promise.all(
      CONTENT_FILES.map((name) => fileRecord(pack, name)),
    ),
  };
}

async function assertClosedPack(pack) {
  const entries = (await readdir(pack, { withFileTypes: true }))
    .map((entry) => {
      if (!entry.isFile() || entry.isSymbolicLink()) {
        throw new Error(
          `Perl pack contains a non-regular entry: ${entry.name}.`,
        );
      }
      return entry.name;
    })
    .sort(compareText);
  if (JSON.stringify(entries) !== JSON.stringify(PACK_FILES)) {
    throw new Error(`Perl pack must contain only: ${PACK_FILES.join(", ")}.`);
  }
}

async function assertPinnedPackFiles(pack, projectRoot) {
  for (const [name, descriptor] of Object.entries(
    PERL_ENGINE_LOCK.prebuilt.files,
  )) {
    const value = await readFile(path.join(pack, name));
    assertBytes(value, descriptor, `Packed ${name}`);
  }
  for (const [name, descriptor] of Object.entries(
    PERL_ENGINE_LOCK.supportingFiles,
  )) {
    const packed = await readFile(path.join(pack, name));
    const source = await readFile(path.join(projectRoot, descriptor.source));
    if (!packed.equals(source)) {
      throw new Error(`Packed ${name} differs from ${descriptor.source}.`);
    }
  }
  const loader = await readFile(path.join(pack, "emperl.js"), "utf8");
  const worker = await readFile(
    path.join(pack, "perl-runtime.worker.js"),
    "utf8",
  );
  const harness = await readFile(
    path.join(pack, "regex_tools_bridge.pl"),
    "utf8",
  );
  dynamicExecutionAudit(loader, worker, harness);
}

const CSP =
  "default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'; connect-src 'self'";

function mediaType(name) {
  if (name.endsWith(".js")) return "text/javascript; charset=utf-8";
  if (name.endsWith(".wasm")) return "application/wasm";
  if (name.endsWith(".json")) return "application/json; charset=utf-8";
  if (name.endsWith(".pl") || name.endsWith(".txt")) {
    return "text/plain; charset=utf-8";
  }
  return "application/octet-stream";
}

async function listen(server) {
  await new Promise((resolve, reject) => {
    server.once("error", reject);
    server.listen(0, "127.0.0.1", resolve);
  });
  const address = server.address();
  if (!address || typeof address === "string") {
    throw new Error("The Perl smoke server did not bind a TCP port.");
  }
  return address.port;
}

async function closeServer(server) {
  server.closeAllConnections?.();
  await new Promise((resolve, reject) => {
    server.close((error) => (error ? reject(error) : resolve()));
  });
}

async function smokePerlPack(pack) {
  const server = createServer(async (request, response) => {
    try {
      const url = new URL(request.url ?? "/", "http://127.0.0.1");
      if (url.pathname === "/") {
        response.writeHead(200, {
          "Content-Type": "text/html; charset=utf-8",
          "Content-Security-Policy": CSP,
          "Cache-Control": "no-store",
          "X-Content-Type-Options": "nosniff",
        });
        response.end(
          "<!doctype html><meta charset=utf-8><title>Perl smoke</title>",
        );
        return;
      }
      const name = decodeURIComponent(url.pathname.slice(1));
      if (!PACK_FILES.includes(name) || name.includes("/")) {
        response.writeHead(404).end("Not found");
        return;
      }
      const value = await readFile(path.join(pack, name));
      response.writeHead(200, {
        "Content-Type": mediaType(name),
        "Content-Security-Policy": CSP,
        "Cache-Control": "no-store",
        "X-Content-Type-Options": "nosniff",
      });
      response.end(value);
    } catch {
      response.writeHead(404).end("Not found");
    }
  });
  const port = await listen(server);
  let browser;
  try {
    const { chromium } = await import("@playwright/test");
    browser = await chromium.launch({ headless: true });
    const page = await browser.newPage();
    const failures = [];
    page.on("pageerror", (error) => failures.push(error.message));
    page.on("console", (message) => {
      if (message.type() === "error") failures.push(message.text());
    });
    const navigation = await page.goto(`http://127.0.0.1:${port}/`);
    const csp = navigation?.headers()["content-security-policy"] ?? "";
    if (
      !csp.includes("'wasm-unsafe-eval'") ||
      /(?:^|[\s;])'unsafe-eval'(?:[\s;]|$)/u.test(csp)
    ) {
      throw new Error("The Perl browser smoke did not use the strict CSP.");
    }
    const result = await page.evaluate(async () => {
      const worker = new Worker("./perl-runtime.worker.js", {
        type: "classic",
      });
      let requestId = 0;
      const send = (kind, request) =>
        new Promise((resolve, reject) => {
          requestId += 1;
          const expected = requestId;
          const cleanup = () => {
            clearTimeout(timer);
            worker.removeEventListener("message", onMessage);
            worker.removeEventListener("error", onError);
          };
          const onError = (event) => {
            cleanup();
            reject(
              new Error(
                event.message || "The WebPerl smoke worker failed to load.",
              ),
            );
          };
          const onMessage = (event) => {
            if (event.data?.requestId !== expected) return;
            cleanup();
            if (event.data.ok) resolve(event.data.payload);
            else reject(new Error(event.data.error?.message ?? "Worker error"));
          };
          const timer = setTimeout(() => {
            cleanup();
            reject(new Error("WebPerl smoke request timed out."));
          }, 45_000);
          worker.addEventListener("message", onMessage);
          worker.addEventListener("error", onError);
          worker.postMessage({
            protocolVersion: 1,
            requestId: expected,
            kind,
            ...(request === undefined ? {} : { request }),
          });
        });
      try {
        const identity = await send("load");
        const base = {
          abi: 1,
          pattern: "(?<word>😀+)",
          flags: "gu",
          options: {},
          subject: "x😀😀 y😀",
          scanAll: false,
          maximumMatches: 10,
          maximumCaptureRows: 10,
        };
        const execution = await send("run", {
          ...base,
          operation: "execute",
        });
        const replacement = await send("run", {
          ...base,
          operation: "replace",
          replacement: "${word}-$1-$$",
          maximumOutputBytes: 1_024,
        });
        const boundedMatches = await send("run", {
          abi: 1,
          operation: "replace",
          pattern: "(a)",
          flags: "gu",
          options: {},
          subject: "a-a-a-tail",
          scanAll: true,
          maximumMatches: 2,
          maximumCaptureRows: 2,
          replacement: "[$1]",
          maximumOutputBytes: 1_024,
        });
        const greedyCapture = await send("run", {
          abi: 1,
          operation: "replace",
          pattern: "(a)(b)",
          flags: "u",
          options: {},
          subject: "ab",
          scanAll: false,
          maximumMatches: 10,
          maximumCaptureRows: 10,
          replacement: "$12|$1",
          maximumOutputBytes: 1_024,
        });
        const adversarialExpansion = await send("run", {
          abi: 1,
          operation: "replace",
          pattern: "(é+)",
          flags: "u",
          options: {},
          subject: "é".repeat(32_768),
          scanAll: false,
          maximumMatches: 1,
          maximumCaptureRows: 1,
          replacement: "$1".repeat(2_048),
          maximumOutputBytes: 31,
        });
        const blocked = await send("run", {
          ...base,
          operation: "execute",
          pattern: "a(?{ die q(pwned) })",
        });
        return {
          identity,
          execution,
          replacement,
          boundedMatches,
          greedyCapture,
          adversarialExpansion,
          blocked,
        };
      } finally {
        worker.terminate();
      }
    });
    if (
      result.identity?.ok !== true ||
      result.identity.identity?.perlVersion !== PERL_ENGINE_LOCK.perlVersion ||
      result.identity.identity?.webPerlVersion !==
        PERL_ENGINE_LOCK.webPerlVersion ||
      result.identity.identity?.legacy !== true ||
      result.identity.identity?.beta !== true ||
      result.identity.identity?.selfTest !== true ||
      result.identity.identity?.replacementTransport !==
        "fixed MEMFS binary output file" ||
      result.identity.identity?.replacementParityOracle !==
        "fixed native s/// fixtures"
    ) {
      throw new Error("WebPerl failed its browser identity smoke test.");
    }
    if (
      result.execution?.ok !== true ||
      result.execution.groupCount !== 1 ||
      JSON.stringify(result.execution.matches?.map(({ span }) => span)) !==
        JSON.stringify([
          [1, 3],
          [5, 6],
        ]) ||
      JSON.stringify(
        result.execution.matches?.map(({ captures }) => captures[0]),
      ) !==
        JSON.stringify([
          [1, 3],
          [5, 6],
        ])
    ) {
      throw new Error("WebPerl failed its native browser match smoke test.");
    }
    if (
      result.replacement?.ok !== true ||
      result.replacement.output !== "x😀😀-😀😀-$ y😀-😀-$" ||
      result.replacement.outputBytes !== 33 ||
      result.replacement.outputTruncated !== false
    ) {
      throw new Error(
        "WebPerl failed its bounded browser replacement smoke test.",
      );
    }
    if (
      result.boundedMatches?.ok !== true ||
      result.boundedMatches.output !== "[a]-[a]-a-tail" ||
      result.boundedMatches.outputBytes !== 14 ||
      result.boundedMatches.outputTruncated !== false ||
      result.boundedMatches.resultsTruncated !== true ||
      result.boundedMatches.matches?.length !== 2
    ) {
      throw new Error(
        "WebPerl replaced beyond its match/capture-row result bound.",
      );
    }
    if (
      result.greedyCapture?.ok !== true ||
      result.greedyCapture.output !== "|a" ||
      result.greedyCapture.outputBytes !== 2 ||
      result.greedyCapture.outputTruncated !== false
    ) {
      throw new Error(
        "WebPerl failed its greedy numbered-capture replacement grammar.",
      );
    }
    if (
      result.adversarialExpansion?.ok !== true ||
      result.adversarialExpansion.output !== "é".repeat(15) ||
      result.adversarialExpansion.outputBytes !== 30 ||
      result.adversarialExpansion.outputTruncated !== true ||
      result.adversarialExpansion.matches?.length !== 1
    ) {
      throw new Error(
        "WebPerl materialized or misbounded an adversarial replacement expansion.",
      );
    }
    if (
      result.blocked?.ok !== false ||
      result.blocked.code !== "code-assertion-disabled"
    ) {
      throw new Error("WebPerl did not reject a runtime regex code assertion.");
    }
    if (failures.length > 0) {
      throw new Error(`WebPerl browser CSP failures: ${failures.join("; ")}`);
    }
  } finally {
    await browser?.close();
    await closeServer(server);
  }
}

export async function verifyPerlPack(
  directory,
  projectRoot = root,
  { smoke = true } = {},
) {
  const repository = await verifyRepositorySources(projectRoot);
  const pack = await assertRealDirectory(directory, "Perl engine pack");
  await assertClosedPack(pack);
  const metadata = await verifyChecksummedEnginePack(pack, "perl");
  await assertPinnedPackFiles(pack, repository);
  const expected = await expectedMetadata(repository, pack);
  if (JSON.stringify(metadata) !== JSON.stringify(expected)) {
    throw new Error(
      "Perl engine metadata does not match the pinned source and pack contract.",
    );
  }
  if (smoke) await smokePerlPack(pack);
  return metadata;
}

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

export async function buildPerlPack({
  projectRoot = root,
  archive,
  prebuiltDirectory,
  output = path.join(root, ".engine-build", "perl"),
  force = false,
} = {}) {
  const repository = await verifyRepositorySources(projectRoot);
  if (Boolean(archive) === Boolean(prebuiltDirectory)) {
    throw new Error(
      "Provide exactly one local --archive or --prebuilt WebPerl input.",
    );
  }
  const upstream = archive
    ? await archiveInputs(path.resolve(archive))
    : await directoryInputs(path.resolve(prebuiltDirectory));
  const target = path.resolve(output);
  assertSafeOutputTarget(target, repository);
  const existing = await lstat(target).catch(() => null);
  if (existing && !force) {
    throw new Error(`${target} exists; pass --force to replace it.`);
  }
  if (existing && (!existing.isDirectory() || existing.isSymbolicLink())) {
    throw new Error("Perl pack output must be a real directory.");
  }
  await mkdir(path.dirname(target), { recursive: true });
  const stage = await mkdtemp(path.join(path.dirname(target), ".perl-pack-"));
  try {
    for (const [name, value] of upstream) {
      await writeFile(path.join(stage, name), value, {
        flag: "wx",
        mode: 0o644,
      });
    }
    for (const [name, descriptor] of Object.entries(
      PERL_ENGINE_LOCK.supportingFiles,
    )) {
      await copyFile(
        path.join(repository, descriptor.source),
        path.join(stage, name),
        fsConstants.COPYFILE_EXCL,
      );
    }
    const metadata = await expectedMetadata(repository, stage);
    await writeFile(
      path.join(stage, "engine-metadata.json"),
      `${JSON.stringify(metadata, null, 2)}\n`,
      { flag: "wx", mode: 0o644 },
    );
    await writeEngineChecksums(stage);
    await verifyPerlPack(stage, repository);
    await replaceDirectory(stage, target, "Perl pack output");
    return {
      output: target,
      metadata: await verifyPerlPack(target, repository, { smoke: false }),
    };
  } catch (error) {
    await rm(stage, { recursive: true, force: true });
    throw error;
  }
}

export async function installPerlPack(source, projectRoot = root) {
  const repository = await verifyRepositorySources(projectRoot);
  const sourcePath = await assertRealDirectory(source, "Perl staged pack");
  const target = path.join(repository, "public", "engines", "perl");
  if (sourcePath === (await realpath(target).catch(() => ""))) {
    throw new Error("Perl staged pack and installation target must differ.");
  }
  const metadata = await verifyPerlPack(sourcePath, repository);
  await mkdir(path.dirname(target), { recursive: true });
  const stage = await mkdtemp(
    path.join(path.dirname(target), ".perl-install-"),
  );
  try {
    for (const name of PACK_FILES) {
      await copyFile(
        path.join(sourcePath, name),
        path.join(stage, name),
        fsConstants.COPYFILE_EXCL,
      );
    }
    await verifyPerlPack(stage, repository, { smoke: false });
    await replaceDirectory(stage, target, "Installed Perl pack target");
  } catch (error) {
    await rm(stage, { recursive: true, force: true });
    throw error;
  }
  return { output: target, metadata };
}

function parseBuildArguments(arguments_) {
  let archive;
  let prebuiltDirectory;
  let output = path.join(root, ".engine-build", "perl");
  let force = false;
  for (let index = 0; index < arguments_.length; index += 1) {
    const argument = arguments_[index];
    if (argument === "--force") {
      if (force) throw new Error("--force was provided more than once.");
      force = true;
      continue;
    }
    if (
      argument === "--archive" ||
      argument === "--prebuilt" ||
      argument === "--output"
    ) {
      const value = arguments_[index + 1];
      if (!value || value.startsWith("--")) {
        throw new Error(`${argument} requires a local path.`);
      }
      if (argument === "--archive") archive = path.resolve(root, value);
      if (argument === "--prebuilt") {
        prebuiltDirectory = path.resolve(root, value);
      }
      if (argument === "--output") output = path.resolve(root, value);
      index += 1;
      continue;
    }
    throw new Error(`Unknown Perl engine-pack argument: ${argument}`);
  }
  return { archive, prebuiltDirectory, output, force };
}

async function main() {
  const arguments_ = process.argv.slice(2);
  if (arguments_[0] === "--verify") {
    if (arguments_.length > 2) {
      throw new Error(
        "Usage: node scripts/perl-engine-pack.mjs --verify [pack]",
      );
    }
    const pack = path.resolve(root, arguments_[1] ?? "public/engines/perl");
    const metadata = await verifyPerlPack(pack, root);
    console.log(`Verified ${metadata.semanticIdentity} pack at ${pack}.`);
    return;
  }
  const result = await buildPerlPack(parseBuildArguments(arguments_));
  console.log(
    `Built verified ${result.metadata.semanticIdentity} pack at ${result.output}.`,
  );
}

if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
  await main();
}
