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 the four implemented production flavour boundaries", () => {
    expect(
      AVAILABLE_REGEX_FLAVOURS.definitions.map((definition) => definition.id),
    ).toEqual(["ecmascript", "pcre2", "python", "java"]);
    expect(
      AVAILABLE_REGEX_FLAVOURS.require("pcre2").options.map(
        (option) => option.name,
      ),
    ).toEqual(["matchLimit", "depthLimit", "heapLimitKib"]);
    expect(() =>
      AVAILABLE_REGEX_FLAVOURS.parse("go", "Project flavour"),
    ).toThrow(/ECMAScript, PCRE2, Python re and Java/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 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("rejects ambiguous registrations at construction time", () => {
    expect(
      () => new RegexFlavourRegistry([fixturePcre2, fixturePcre2]),
    ).toThrow(/registered more than once/u);
  });
});
