// Each input must produce exactly one className fact (always dynamic) and
// never throw. Binding regression bar for "extraction never aborts a scan."
import { describe, expect, it } from "vitest";

import { classnameFacts } from "./helpers.js";

describe("extractClassnames — §1.7 adversarial inputs never throw", () => {
  it("immediately-invoked arrow function (IIFE) → one classname_dynamic", () => {
    const facts = classnameFacts(
      `export default function P() {
        return <div className={(() => 'x')()}>x</div>;
      }`,
    );
    expect(facts).toHaveLength(1);
    expect(facts[0].kind).toBe("classname_dynamic");
  });

  it("className={null} → one classname_dynamic (null literal)", () => {
    const facts = classnameFacts(
      `export default function P() {
        return <div className={null}>x</div>;
      }`,
    );
    expect(facts).toHaveLength(1);
    expect(facts[0].kind).toBe("classname_dynamic");
  });

  it("className={undefined} → one classname_dynamic (undefined identifier)", () => {
    const facts = classnameFacts(
      `export default function P() {
        return <div className={undefined}>x</div>;
      }`,
    );
    expect(facts).toHaveLength(1);
    expect(facts[0].kind).toBe("classname_dynamic");
  });

  it("deeply nested parens → resolves to the inner identifier", () => {
    const facts = classnameFacts(
      `export default function P({ nested }: any) {
        return <div className={(((nested)))}>x</div>;
      }`,
    );
    expect(facts).toHaveLength(1);
    expect(facts[0]).toMatchObject({
      kind: "classname_dynamic",
      reason: "identifier",
    });
  });

  it("logical chains with nullish + member + call + ternary all coexist", () => {
    const facts = classnameFacts(
      `export default function P({ user, active, sizes }: any) {
        return (
          <div
            className={
              user?.profile?.classes ??
                (active ? sizes.lg : sizes['sm']) ??
                'fallback-class'
            }
          >x</div>
        );
      }`,
    );
    // No throw; at minimum captures the "fallback-class" literal.
    const literal = facts.find((f) => f.kind === "classname_literal");
    expect(literal).toBeDefined();
    if (literal && literal.kind === "classname_literal") {
      expect(literal.classes).toContain("fallback-class");
    }
  });

  it("expression with parse-time edge cases (TS satisfies, as cast) → no throw", () => {
    const facts = classnameFacts(
      `export default function P({ classes }: any) {
        return <div className={(classes as string) ?? 'x y'}>x</div>;
      }`,
    );
    const literal = facts.find((f) => f.kind === "classname_literal");
    expect(literal).toBeDefined();
    if (literal && literal.kind === "classname_literal") {
      expect(literal.classes).toEqual(["x", "y"]);
    }
  });
});

describe("extractClassnames — invalid TSX", () => {
  it("unparseable file returns no facts (extractor swallows parse errors)", () => {
    const facts = classnameFacts(`this is not (((( valid tsx`);
    expect(facts).toHaveLength(0);
  });
});
