import { describe, expect, it } from "vitest";
import { generatePcre2C, type Pcre2CGenerationRequest } from "./pcre2-c";

function request(
  values: Partial<Pcre2CGenerationRequest> = {},
): Pcre2CGenerationRequest {
  return {
    flavour: "pcre2",
    flavourVersion: "PCRE2 10.47 8-bit WebAssembly",
    operation: "match",
    pattern: "(?<value>.+)",
    flags: ["g", "i", "m", "s", "x", "U", "J"],
    options: {
      matchLimit: 1_000_000,
      depthLimit: 1_000,
      heapLimitKib: 32_768,
    },
    subject: "value",
    scanAll: true,
    maximumMatches: 1_000,
    maximumCaptureRows: 10_000,
    maximumOutputBytes: 1024 * 1024,
    ...values,
  };
}

describe("PCRE2 10.47 C generator", () => {
  it("emits exact UTF-8 byte arrays instead of interpolating host literals", () => {
    const pattern = '(?<x>["\\\\${}]+\\R)';
    const subject = "“quote” \\\nGrüße ${value}\0";
    const replacement = '${x} "$" \\\\';
    const generated = generatePcre2C(
      request({
        operation: "replace",
        pattern,
        subject,
        replacement,
      }),
    );

    expect(generated).toMatchObject({
      target: "c17-pcre2-8",
      fileName: "regex-tools-pcre2-replace.c",
      engineIdentity: "PCRE2 10.47 8-bit",
      represents: "replacement",
    });
    expect(generated.source).toContain(
      '#error "This generated program requires PCRE2 10.47 exactly."',
    );
    expect(generated.source).toContain(
      "PCRE2_UTF | PCRE2_UCP | PCRE2_CASELESS | PCRE2_MULTILINE | PCRE2_DOTALL | PCRE2_EXTENDED | PCRE2_UNGREEDY | PCRE2_DUPNAMES",
    );
    expect(generated.source).toContain(
      "static const PCRE2_UCHAR replacement[] = {",
    );
    expect(generated.source).toContain(
      "0x47, 0x72, 0xc3, 0xbc, 0xc3, 0x9f, 0x65",
    );
    expect(generated.source).not.toContain(pattern);
    expect(generated.source).not.toContain(subject);
    expect(generated.source).not.toContain(replacement);
    expect(generated.caveats.join(" ")).toMatch(
      /checked only after substitution/u,
    );
    expect(generated.caveats.join(" ")).toMatch(/wall-clock timeout/u);
  });

  it("states and implements application-level all-match iteration", () => {
    const generated = generatePcre2C(
      request({
        flags: [],
        scanAll: true,
      }),
    );

    expect(generated.represents).toBe("all-matches");
    expect(generated.source).toContain("static PCRE2_SIZE advance_utf8");
    expect(generated.source).toContain(
      "PCRE2_NOTEMPTY_ATSTART | PCRE2_ANCHORED",
    );
    expect(generated.source).toContain(
      "Regex Tools host-side match/capture result cap reached.",
    );
    expect(generated.source).toContain(
      "pcre2_set_match_limit(context, match_limit)",
    );
    expect(generated.source).toContain(
      "pcre2_set_depth_limit(context, depth_limit)",
    );
    expect(generated.source).toContain(
      "pcre2_set_heap_limit(context, heap_limit_kib)",
    );
  });

  it("generates a first-match program only when neither g nor scan-all is selected", () => {
    const generated = generatePcre2C(request({ flags: ["i"], scanAll: false }));

    expect(generated.represents).toBe("first-match");
    expect(generated.source).toContain("if (!false)");
    expect(generated.caveats).toContain(
      "The program requests only the first match.",
    );
  });

  it("fails closed on unsupported mappings and lossy browser Unicode", () => {
    expect(() => generatePcre2C(request({ flags: ["u"] }))).toThrow(
      /unsupported/u,
    );
    expect(() => generatePcre2C(request({ pattern: "\ud800" }))).toThrow(
      /unpaired UTF-16 surrogate/u,
    );
    expect(() =>
      generatePcre2C(request({ operation: "replace", replacement: undefined })),
    ).toThrow(/requires an exact replacement/u);
  });
});
