import { describe, expect, it } from "vitest";
import type {
  RegexExecutionRequest,
  RegexReplacementRequest,
} from "../../../model/match";
import { CppEngineAdapter, CPP_FLAVOUR_VERSION } from "./CppEngineAdapter";
import type { CppRegexModule } from "./cpp-module";

function request(
  overrides: Partial<RegexExecutionRequest> = {},
): RegexExecutionRequest {
  return {
    flavour: "cpp",
    flavourVersion: CPP_FLAVOUR_VERSION,
    pattern: "(\\w+)",
    flags: ["g"],
    options: { grammar: "ECMAScript" },
    subject: "😀 one",
    captureMetadata: [],
    scanAll: true,
    maximumMatches: 10,
    maximumCaptureRows: 100,
    ...overrides,
  };
}

function moduleWith(run: CppRegexModule["regexToolsCppRun"]): CppRegexModule {
  return {
    regexToolsCppIdentity: () => ({
      implementation: "libc++ std::wregex",
      cplusplus: 202002,
      emscripten: "6.0.4",
      wcharBits: 32,
      replacementSelfTest: true,
    }),
    regexToolsCppRun: run,
  };
}

describe("C++ std::regex adapter", () => {
  it("preserves code-point native offsets and normalizes UTF-16 ranges", async () => {
    const adapter = new CppEngineAdapter(
      moduleWith(() => ({
        abi: 1,
        ok: true,
        groupCount: 1,
        groupNames: [],
        matches: [{ span: [2, 5], captures: [[2, 5]] }],
        resultsTruncated: false,
      })),
    );

    const result = await adapter.execute(request());

    expect(result.accepted).toBe(true);
    expect(result.engine.engineName).toContain("std::wregex");
    expect(result.matches[0]).toMatchObject({
      value: "one",
      range: { startUtf16: 3, endUtf16: 6 },
      nativeRange: { start: 2, end: 5, unit: "code-point" },
      captures: [
        {
          groupNumber: 1,
          value: "one",
          range: { startUtf16: 3, endUtf16: 6 },
        },
      ],
    });
  });

  it("returns native bounded replacement output", async () => {
    const adapter = new CppEngineAdapter(
      moduleWith(() => ({
        abi: 1,
        ok: true,
        groupCount: 1,
        groupNames: [],
        matches: [{ span: [0, 3], captures: [[0, 3]] }],
        resultsTruncated: false,
        output: "<one>",
        outputBytes: 5,
        outputTruncated: false,
      })),
    );
    const replacement: RegexReplacementRequest = {
      ...request({ subject: "one" }),
      replacement: "<$1>",
      maximumOutputBytes: 100,
    };

    await expect(adapter.replace(replacement)).resolves.toMatchObject({
      output: "<one>",
      outputBytes: 5,
      outputTruncated: false,
      execution: { accepted: true },
    });
  });

  it("rejects identities, versions and strings that cannot round-trip", async () => {
    const invalidIdentity = new CppEngineAdapter({
      regexToolsCppIdentity: () => ({
        implementation: "another library",
      }),
      regexToolsCppRun: () => ({}),
    });
    await expect(invalidIdentity.load()).rejects.toThrow("identity self-test");

    const adapter = new CppEngineAdapter(
      moduleWith(() => {
        throw new Error("must not execute");
      }),
    );
    await expect(
      adapter.execute(request({ flavourVersion: "another version" })),
    ).rejects.toThrow("unsupported");
    await expect(
      adapter.execute(request({ subject: "\ud800" })),
    ).rejects.toThrow("unpaired UTF-16 surrogate");
  });
});
