// @vitest-environment node

import { readFile } from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { beforeAll, describe, expect, it } from "vitest";
import {
  CppEngineAdapter,
  CPP_ENGINE_VERSION,
  CPP_FLAVOUR_VERSION,
} from "../../src/regex/execution/adapters/cpp/CppEngineAdapter";
import type { CppRegexModuleFactory } from "../../src/regex/execution/adapters/cpp/cpp-module";
import type {
  RegexExecutionRequest,
  RegexReplacementRequest,
} from "../../src/regex/model/match";

const pack = path.resolve("public/engines/cpp");
let adapter: CppEngineAdapter;

function request(
  pattern: string,
  subject: string,
  overrides: Partial<RegexExecutionRequest> = {},
): RegexExecutionRequest {
  return {
    flavour: "cpp",
    flavourVersion: CPP_FLAVOUR_VERSION,
    pattern,
    flags: ["g"],
    options: { grammar: "ECMAScript" },
    subject,
    captureMetadata: [],
    scanAll: false,
    maximumMatches: 100,
    maximumCaptureRows: 1_000,
    ...overrides,
  };
}

function replacement(
  pattern: string,
  subject: string,
  replacementText: string,
  overrides: Partial<RegexReplacementRequest> = {},
): RegexReplacementRequest {
  return {
    ...request(pattern, subject),
    replacement: replacementText,
    maximumOutputBytes: 1_024,
    ...overrides,
  };
}

beforeAll(async () => {
  const imported = (await import(
    `${pathToFileURL(path.join(pack, "cpp-regex.mjs")).href}?conformance`
  )) as { default: CppRegexModuleFactory };
  const module = await imported.default({
    locateFile: (name) => path.join(pack, name),
    print: () => undefined,
    printErr: () => undefined,
  });
  adapter = new CppEngineAdapter(module, "Node.js conformance");
  await adapter.load();
});

describe("C++ Emscripten libc++ std::wregex conformance", () => {
  it("loads the exact libc++ and compiler identity", async () => {
    await expect(adapter.load()).resolves.toEqual(
      expect.objectContaining({
        flavour: "cpp",
        engineName: "C++ std::wregex (Emscripten libc++)",
        engineVersion: CPP_ENGINE_VERSION,
        runtimeVersion: expect.stringContaining(
          "32-bit wchar_t code-point bridge",
        ),
        offsetUnit: "code-point",
      }),
    );
  });

  it("returns code-point-native ranges without losing editor UTF-16 offsets", async () => {
    const result = await adapter.execute(request("(\\w+)", "😀 one two"));

    expect(result.accepted).toBe(true);
    expect(
      result.matches.map(({ nativeRange, range, value }) => ({
        nativeRange,
        range,
        value,
      })),
    ).toEqual([
      {
        nativeRange: { start: 2, end: 5, unit: "code-point" },
        range: { startUtf16: 3, endUtf16: 6 },
        value: "one",
      },
      {
        nativeRange: { start: 6, end: 9, unit: "code-point" },
        range: { startUtf16: 7, endUtf16: 10 },
        value: "two",
      },
    ]);
  });

  it("uses native std::regex replacement and grammar selection", async () => {
    const replaced = await adapter.replace(
      replacement("(\\w+)", "one two", "<$1>"),
    );
    expect(replaced.execution.accepted).toBe(true);
    expect(replaced.output).toBe("<one> <two>");

    const basic = await adapter.execute(
      request("\\(ab\\)", "xxabyy", {
        options: { grammar: "basic" },
      }),
    );
    expect(basic.matches[0]?.value).toBe("ab");
  });

  it("bounds capture amplification and preserves the untouched partial suffix", async () => {
    const bounded = await adapter.replace(
      replacement("(a+)", "a".repeat(256 * 1024), "$1".repeat(4_096), {
        maximumOutputBytes: 5,
      }),
    );
    expect(bounded.execution.accepted).toBe(true);
    expect(bounded.output).toBe("aaaaa");
    expect(bounded.outputBytes).toBe(5);
    expect(bounded.outputTruncated).toBe(true);

    const partial = await adapter.replace(
      replacement("a", "aaa", "x", { maximumMatches: 1 }),
    );
    expect(partial.execution.matches).toHaveLength(1);
    expect(partial.execution.truncated).toBe(true);
    expect(partial.output).toBe("xaa");
    expect(partial.outputTruncated).toBe(false);
  });

  it("reports syntax that libc++ std::regex does not accept", async () => {
    const result = await adapter.execute(request("(?<name>a)", "a"));
    expect(result.accepted).toBe(false);
    expect(result.diagnostics[0]).toEqual(
      expect.objectContaining({
        source: "execution-engine",
        severity: "error",
      }),
    );
  });

  it("ships a fixed bridge module without dynamic source evaluation", async () => {
    const module = await readFile(path.join(pack, "cpp-regex.mjs"), "utf8");
    expect(module).not.toContain("new Function");
    expect(module).not.toMatch(/\beval\s*\(/u);

    const metadata = JSON.parse(
      await readFile(path.join(pack, "engine-metadata.json"), "utf8"),
    ) as {
      bridge: { dynamicExecutionDisabled: boolean };
      toolchain: { version: string; commitSha1: string };
    };
    expect(metadata.bridge.dynamicExecutionDisabled).toBe(true);
    expect(metadata.toolchain).toEqual(
      expect.objectContaining({
        version: "6.0.4",
        commitSha1: "fe5be6afdff43ad58860d821fcc8572a23f92d19",
      }),
    );
  });
});
