import { describe, expect, it } from "vitest";
import {
  benchmarkEcmaScriptSample,
  ecmaScriptAnalysisIdentity,
  probeEcmaScriptGrowth,
} from "./ecmascript-analysis";

function benchmarkRequest(
  overrides: Partial<Parameters<typeof benchmarkEcmaScriptSample>[0]> = {},
) {
  return {
    flavour: "ecmascript" as const,
    pattern: "\\d+",
    flags: ["g"],
    subject: "12 345",
    replacement: "[$&]",
    scanAll: true,
    maximumMatches: 10_000,
    ...overrides,
  };
}

describe("native ECMAScript analysis worker primitives", () => {
  it("retains exact runtime identity and separates benchmark phases", () => {
    const identity = ecmaScriptAnalysisIdentity();
    const sample = benchmarkEcmaScriptSample(benchmarkRequest());

    expect(identity).toEqual(
      expect.objectContaining({
        flavour: "ecmascript",
        engineName: "Native ECMAScript RegExp",
        nativeOffsetUnit: "utf16",
      }),
    );
    expect(sample).toEqual(
      expect.objectContaining({
        accepted: true,
        effectiveFlags: expect.stringContaining("g"),
        matchCount: 2,
        matched: true,
        matchCollectionTruncated: false,
        replacementOutputUtf16: 10,
      }),
    );
    expect(sample.compileMs).toBeGreaterThanOrEqual(0);
    expect(sample.firstMatchMs).toBeGreaterThanOrEqual(0);
    expect(sample.allMatchesMs).toBeGreaterThanOrEqual(0);
    expect(sample.replacementMs).toBeGreaterThanOrEqual(0);
    expect(sample.throughputBytesPerSecond).toBeGreaterThanOrEqual(0);
  });

  it("bounds zero-length global iteration", () => {
    const sample = benchmarkEcmaScriptSample(
      benchmarkRequest({
        pattern: "(?=.)",
        flags: ["g", "u"],
        subject: "😀x",
        replacement: "",
      }),
    );

    expect(sample.matchCount).toBe(2);
    expect(sample.matchCollectionTruncated).toBe(false);
  });

  it("preserves single sticky execution unless scan-all is explicit", () => {
    const single = benchmarkEcmaScriptSample(
      benchmarkRequest({
        pattern: "a",
        flags: ["y"],
        subject: "aa",
        replacement: "x",
        scanAll: false,
      }),
    );
    const scanAll = benchmarkEcmaScriptSample(
      benchmarkRequest({
        pattern: "a",
        flags: ["y"],
        subject: "aa",
        replacement: "x",
        scanAll: true,
      }),
    );

    expect(single.matchCount).toBe(1);
    expect(scanAll.matchCount).toBe(2);
    expect(scanAll.replacementMs).toBeUndefined();
    expect(scanAll.replacementSkippedReason).toContain(
      "multi-match sticky replacement",
    );
  });

  it("returns compile rejection as data", () => {
    const sample = benchmarkEcmaScriptSample(
      benchmarkRequest({ pattern: "(", subject: "" }),
    );
    const growth = probeEcmaScriptGrowth({
      flavour: "ecmascript",
      pattern: "(",
      flags: [],
      subject: "",
      scanAll: false,
      maximumMatches: 10,
    });

    expect(sample.accepted).toBe(false);
    expect(sample.compileError).toBeTruthy();
    expect(growth).toEqual(
      expect.objectContaining({
        accepted: false,
        compileError: expect.any(String),
      }),
    );
  });

  it("skips replacement timing before a conservative output can grow beyond its bound", () => {
    const sample = benchmarkEcmaScriptSample(
      benchmarkRequest({
        pattern: "a",
        flags: ["g"],
        subject: "a".repeat(4_096),
        replacement: "$`$'",
        maximumMatches: 10_000,
      }),
    );

    expect(sample.replacementMs).toBeUndefined();
    expect(sample.replacementSkippedReason).toContain(
      "Conservative output estimate",
    );
  });

  it("does not run unbounded replacement work after match collection truncates", () => {
    const sample = benchmarkEcmaScriptSample(
      benchmarkRequest({
        pattern: "a",
        flags: ["g"],
        subject: "aaaa",
        maximumMatches: 1,
      }),
    );

    expect(sample.matchCollectionTruncated).toBe(true);
    expect(sample.replacementMs).toBeUndefined();
    expect(sample.replacementSkippedReason).toContain(
      "Match collection reached its bound",
    );
  });

  it("rejects flags outside the supported ECMAScript set", () => {
    expect(() =>
      benchmarkEcmaScriptSample(
        benchmarkRequest({ flags: ["g", "unsupported"] }),
      ),
    ).toThrow(/Unsupported ECMAScript analysis flag/u);
  });

  it("reports bounded growth probes without materializing match values", () => {
    const sample = probeEcmaScriptGrowth({
      flavour: "ecmascript",
      pattern: "a+$",
      flags: [],
      subject: "aaaa!",
      scanAll: false,
      maximumMatches: 10,
    });

    expect(sample).toEqual(
      expect.objectContaining({
        accepted: true,
        subjectBytes: 5,
        subjectUtf16: 5,
        matched: false,
        matchCount: 0,
      }),
    );
    expect(sample.executionMs).toBeGreaterThanOrEqual(0);
  });
});
