import { describe, expect, it } from "vitest";
import {
  AVAILABLE_REGEX_FLAVOURS,
  RegexFlavourRegistry,
  defaultRegexOptions,
  parseRegexFlags,
  parseRegexOptions,
  resolveRegexFlavourVersion,
  type RegexFlavourDefinition,
} from "./flavour-registry";

const fixturePcre2: RegexFlavourDefinition = {
  id: "pcre2",
  label: "Fixture PCRE2",
  versions: [
    {
      value: "fixture-1",
      label: "Fixture 1",
      syntaxVersion: "fixture-1",
    },
  ],
  defaultVersion: "fixture-1",
  flags: [
    { value: "i", label: "Caseless" },
    { value: "x", label: "Extended" },
  ],
  defaultFlags: [],
  options: [
    {
      name: "matchLimit",
      label: "Match limit",
      kind: "integer",
      defaultValue: 100_000,
      minimum: 1,
      maximum: 1_000_000,
    },
    {
      name: "ungreedy",
      label: "Ungreedy",
      kind: "boolean",
      defaultValue: false,
    },
  ],
};

describe("available regex flavour contracts", () => {
  it("publishes every requested profile plus the standalone PCRE2 boundary", () => {
    expect(
      AVAILABLE_REGEX_FLAVOURS.definitions.map((definition) => definition.id),
    ).toEqual([
      "ecmascript",
      "pcre2",
      "php",
      "perl",
      "python",
      "ruby",
      "java",
      "cpp",
      "go",
      "dotnet",
      "rust",
      "scala",
    ]);
    expect(AVAILABLE_REGEX_FLAVOURS.require("ecmascript").label).toBe(
      "JavaScript (ECMAScript)",
    );
    expect(
      AVAILABLE_REGEX_FLAVOURS.require("pcre2").options.map(
        (option) => option.name,
      ),
    ).toEqual(["matchLimit", "depthLimit", "heapLimitKib"]);
    expect(() =>
      AVAILABLE_REGEX_FLAVOURS.parse("unknown", "Project flavour"),
    ).toThrow(/unavailable/u);
  });

  it("validates ECMAScript flags through the flavour contract", () => {
    const definition = AVAILABLE_REGEX_FLAVOURS.require("ecmascript");

    expect(parseRegexFlags(["g", "u"], definition, "Flags")).toEqual([
      "g",
      "u",
    ]);
    expect(() => parseRegexFlags(["u", "v"], definition, "Flags")).toThrow(
      /cannot be combined/u,
    );
    expect(() => parseRegexFlags(["x"], definition, "Flags")).toThrow(
      /unsupported/u,
    );
    expect(
      resolveRegexFlavourVersion(
        "ECMAScript 2025",
        definition,
        "Flavour version",
      ),
    ).toEqual({
      definition: definition.versions[0],
      value: "ECMAScript 2025",
    });
    expect(
      resolveRegexFlavourVersion(undefined, definition, "Flavour version")
        .value,
    ).toBe("ECMAScript 2025 syntax / current browser runtime");
    expect(() =>
      resolveRegexFlavourVersion("2026", definition, "Flavour version"),
    ).toThrow(/unsupported for JavaScript \(ECMAScript\)/u);
  });

  it("supports typed future option contracts without registering execution", () => {
    const registry = new RegexFlavourRegistry([fixturePcre2]);
    const definition = registry.require("pcre2");

    expect(defaultRegexOptions(definition)).toEqual({
      matchLimit: 100_000,
      ungreedy: false,
    });
    expect(parseRegexOptions(undefined, definition, "Fixture options")).toEqual(
      {
        matchLimit: 100_000,
        ungreedy: false,
      },
    );
    expect(
      parseRegexOptions(
        { matchLimit: 250_000, ungreedy: true },
        definition,
        "Fixture options",
      ),
    ).toEqual({ matchLimit: 250_000, ungreedy: true });
    expect(() =>
      parseRegexOptions({ matchLimit: 0 }, definition, "Fixture options"),
    ).toThrow(/integer from 1 to 1000000/u);
    expect(() =>
      parseRegexOptions({ jit: true }, definition, "Fixture options"),
    ).toThrow(/unsupported entries: jit/u);
  });

  it("validates production PCRE2 flags, version and bounded limits", () => {
    const definition = AVAILABLE_REGEX_FLAVOURS.require("pcre2");
    expect(parseRegexFlags(["g", "J"], definition, "Flags")).toEqual([
      "g",
      "J",
    ]);
    expect(defaultRegexOptions(definition)).toEqual({
      matchLimit: 1_000_000,
      depthLimit: 1_000,
      heapLimitKib: 32_768,
    });
    expect(
      parseRegexOptions(
        { matchLimit: 50_000, depthLimit: 50, heapLimitKib: 1_024 },
        definition,
        "PCRE2 options",
      ),
    ).toEqual({
      matchLimit: 50_000,
      depthLimit: 50,
      heapLimitKib: 1_024,
    });
    expect(() =>
      parseRegexOptions(
        { matchLimit: 100_000_001 },
        definition,
        "PCRE2 options",
      ),
    ).toThrow(/integer from 1 to 100000000/u);
  });

  it("validates Python and Java flavour identities and flags", () => {
    const python = AVAILABLE_REGEX_FLAVOURS.require("python");
    const java = AVAILABLE_REGEX_FLAVOURS.require("java");

    expect(parseRegexFlags(["g", "a", "x"], python, "Flags")).toEqual([
      "g",
      "a",
      "x",
    ]);
    expect(
      resolveRegexFlavourVersion("Python 3.14", python, "Version").value,
    ).toBe("Python 3.14");
    expect(parseRegexFlags(["g", "d", "U"], java, "Flags")).toEqual([
      "g",
      "d",
      "U",
    ]);
    expect(
      resolveRegexFlavourVersion("TeaVM 0.15.0", java, "Version").value,
    ).toBe("TeaVM 0.15.0");
    expect(() => parseRegexFlags(["a"], java, "Flags")).toThrow(/unsupported/u);
  });

  it("publishes pinned identities and honest compatibility labels for the additional profiles", () => {
    expect(AVAILABLE_REGEX_FLAVOURS.require("php").defaultVersion).toBe(
      "PHP 8.5.8 preg / PCRE2 10.44",
    );
    expect(AVAILABLE_REGEX_FLAVOURS.require("perl").defaultVersion).toBe(
      "Perl 5.28.1 via WebPerl 0.09-beta",
    );
    expect(AVAILABLE_REGEX_FLAVOURS.require("ruby").defaultVersion).toBe(
      "CRuby 4.0.0 WebAssembly",
    );
    expect(AVAILABLE_REGEX_FLAVOURS.require("go").defaultVersion).toBe(
      "Go 1.26.5 standard-library regexp",
    );
    expect(AVAILABLE_REGEX_FLAVOURS.require("rust").defaultVersion).toBe(
      "Rust regex 1.13.1 WebAssembly",
    );
    expect(AVAILABLE_REGEX_FLAVOURS.require("dotnet").defaultVersion).toBe(
      ".NET 10 System.Text.RegularExpressions",
    );
    expect(AVAILABLE_REGEX_FLAVOURS.require("scala").label).toContain(
      "Java compatibility",
    );
  });

  it("models C++ grammar selection separately from syntax-option flags", () => {
    const cpp = AVAILABLE_REGEX_FLAVOURS.require("cpp");
    expect(cpp.defaultVersion).toBe("C++ std::regex · Emscripten 6.0.4 libc++");
    expect(cpp.flags.map((flag) => flag.value)).toEqual([
      "g",
      "i",
      "n",
      "o",
      "c",
    ]);
    expect(defaultRegexOptions(cpp)).toEqual({ grammar: "ECMAScript" });
    expect(parseRegexOptions({ grammar: "egrep" }, cpp, "C++ options")).toEqual(
      { grammar: "egrep" },
    );
    expect(() =>
      parseRegexOptions({ grammar: "perl" }, cpp, "C++ options"),
    ).toThrow(/unsupported text value/u);
  });

  it("rejects ambiguous registrations at construction time", () => {
    expect(
      () => new RegexFlavourRegistry([fixturePcre2, fixturePcre2]),
    ).toThrow(/registered more than once/u);
  });
});
