import { describe, expect, it } from "vitest";
import { EcmaScriptSyntaxProvider } from "../../src/regex/syntax/providers/ecmascript/EcmaScriptSyntaxProvider";
import { executeEcmaScript } from "../../src/regex/execution/adapters/ecmascript/EcmaScriptEngineAdapter";

const provider = new EcmaScriptSyntaxProvider();

const cases = [
  {
    name: "named capture",
    pattern: "(?<name>\\w+)",
    flags: ["u"],
    subject: "alice",
    value: "alice",
  },
  {
    name: "lookbehind",
    pattern: "(?<=€)\\d+",
    flags: ["u"],
    subject: "€42",
    value: "42",
  },
  {
    name: "backreference",
    pattern: "(?<word>\\w+)\\s+\\k<word>",
    flags: ["u"],
    subject: "echo echo",
    value: "echo echo",
  },
  {
    name: "Unicode property",
    pattern: "\\p{Letter}+",
    flags: ["u"],
    subject: "Grüße 42",
    value: "Grüße",
  },
] as const;

describe("project-authored ECMAScript conformance cases", () => {
  for (const testCase of cases) {
    it(testCase.name, async () => {
      const syntax = await provider.parsePattern({
        flavour: "ecmascript",
        pattern: testCase.pattern,
        flags: testCase.flags,
        options: {},
      });
      expect(syntax.accepted).toBe(true);
      const result = executeEcmaScript({
        flavour: "ecmascript",
        pattern: testCase.pattern,
        flags: testCase.flags,
        subject: testCase.subject,
        captureMetadata: syntax.captures,
        scanAll: false,
        maximumMatches: 100,
        maximumCaptureRows: 1_000,
      });
      expect(result.accepted).toBe(true);
      expect(result.matches[0]?.value).toBe(testCase.value);
    });
  }

  it("keeps astral ranges in UTF-16 code units", async () => {
    const syntax = await provider.parsePattern({
      flavour: "ecmascript",
      pattern: "(?<emoji>😀)",
      flags: ["u"],
      options: {},
    });
    const result = executeEcmaScript({
      flavour: "ecmascript",
      pattern: "(?<emoji>😀)",
      flags: ["u"],
      subject: "x😀y",
      captureMetadata: syntax.captures,
      scanAll: false,
      maximumMatches: 10,
      maximumCaptureRows: 10,
    });
    expect(result.matches[0]?.range).toEqual({
      startUtf16: 1,
      endUtf16: 3,
    });
    expect(result.matches[0]?.captures[0]?.nativeRange).toEqual({
      start: 1,
      end: 3,
      unit: "utf16",
    });
  });

  it.each([
    {
      name: "global iteration",
      pattern: "\\d",
      flags: ["g"],
      subject: "1 2",
      values: ["1", "2"],
    },
    {
      name: "sticky iteration",
      pattern: "\\w",
      flags: ["y"],
      subject: "ab",
      values: ["a"],
    },
    {
      name: "ignore case",
      pattern: "hello",
      flags: ["i"],
      subject: "HELLO",
      values: ["HELLO"],
    },
    {
      name: "multiline anchors",
      pattern: "^second$",
      flags: ["m"],
      subject: "first\nsecond\nthird",
      values: ["second"],
    },
    {
      name: "dot all",
      pattern: "a.b",
      flags: ["s"],
      subject: "a\nb",
      values: ["a\nb"],
    },
    {
      name: "Unicode sets",
      pattern: "[\\p{Letter}&&\\p{ASCII}]+",
      flags: ["v"],
      subject: "abcä",
      values: ["abc"],
    },
  ])("implements $name flag semantics", async (testCase) => {
    const syntax = await provider.parsePattern({
      flavour: "ecmascript",
      pattern: testCase.pattern,
      flags: testCase.flags,
      options: {},
    });
    expect(syntax.accepted).toBe(true);
    const result = executeEcmaScript({
      flavour: "ecmascript",
      pattern: testCase.pattern,
      flags: testCase.flags,
      subject: testCase.subject,
      captureMetadata: syntax.captures,
      scanAll: false,
      maximumMatches: 100,
      maximumCaptureRows: 1_000,
    });
    expect(result.accepted).toBe(true);
    expect(result.matches.map((match) => match.value)).toEqual(testCase.values);
  });

  it("preserves an explicit user indices flag", async () => {
    const syntax = await provider.parsePattern({
      flavour: "ecmascript",
      pattern: "(a)",
      flags: ["d"],
      options: {},
    });
    const result = executeEcmaScript({
      flavour: "ecmascript",
      pattern: "(a)",
      flags: ["d"],
      subject: "a",
      captureMetadata: syntax.captures,
      scanAll: false,
      maximumMatches: 10,
      maximumCaptureRows: 10,
    });

    expect(result.accepted).toBe(true);
    expect(result.flags.userFlags).toBe("d");
    expect(result.flags.internallyAddedIndicesFlag).toBe(false);
    expect(result.matches[0]?.captures[0]?.range).toEqual({
      startUtf16: 0,
      endUtf16: 1,
    });
  });

  it("reports malformed syntax from both provider and engine", async () => {
    const syntax = await provider.parsePattern({
      flavour: "ecmascript",
      pattern: "(",
      flags: [],
      options: {},
    });
    expect(syntax.accepted).toBe(false);
    expect(syntax.diagnostics[0]?.range).toEqual({
      startUtf16: 1,
      endUtf16: 1,
    });

    const execution = executeEcmaScript({
      flavour: "ecmascript",
      pattern: "(",
      flags: [],
      subject: "",
      captureMetadata: [],
      scanAll: false,
      maximumMatches: 10,
      maximumCaptureRows: 10,
    });
    expect(execution.accepted).toBe(false);
    expect(execution.diagnostics[0]?.code).toBe("compile-error");
  });

  it("records the current runtime's duplicate-name behavior", async () => {
    const pattern = "(?<value>a)|(?<value>b)";
    const syntax = await provider.parsePattern({
      flavour: "ecmascript",
      pattern,
      flags: [],
      options: {},
    });
    const execution = executeEcmaScript({
      flavour: "ecmascript",
      pattern,
      flags: [],
      subject: "b",
      captureMetadata: syntax.captures,
      scanAll: false,
      maximumMatches: 10,
      maximumCaptureRows: 10,
    });
    let runtimeSupportsDuplicateNames = true;
    try {
      new RegExp(pattern);
    } catch {
      runtimeSupportsDuplicateNames = false;
    }

    expect(execution.accepted).toBe(runtimeSupportsDuplicateNames);
    if (runtimeSupportsDuplicateNames) {
      expect(
        execution.matches[0]?.captures.find(
          (capture) =>
            capture.groupName === "value" &&
            capture.status !== "did-not-participate",
        )?.value,
      ).toBe("b");
    } else {
      expect(execution.diagnostics[0]?.code).toBe("compile-error");
    }
  });
});
