import { describe, expect, it } from "vitest";

import { factId } from "@fragments-sdk/core";

import { extractJsxFacts } from "../../usage/jsx.js";
import { parseClass } from "../parse-class.js";

const FILE = "src/component.tsx";
const NODE_ID = factId("usage_node", {
  file: FILE,
  nodePath: "0:0",
  element: "div",
});
const LOCATION = { file: FILE, line: 1, column: 0 };

function parsed(raw: string) {
  return parseClass({
    raw,
    file: FILE,
    nodeId: NODE_ID,
    originPath: `test/${raw}`,
    location: LOCATION,
  });
}

describe("parseClass — ACCEPTANCE §2.10–§2.16", () => {
  it("§2.10 — parses a plain utility-token", () => {
    expect(parsed("bg-red-500")).toMatchObject({
      kind: "tailwind_class",
      raw: "bg-red-500",
      utility: "bg",
      modifiers: [],
      important: false,
      negative: false,
      prefix: null,
      value: { kind: "token", token: "red-500" },
    });
  });

  it("§2.11 — parses arbitrary values", () => {
    expect(parsed("m-[15%]")).toMatchObject({
      utility: "m",
      modifiers: [],
      important: false,
      negative: false,
      prefix: null,
      value: { kind: "arbitrary", raw: "15%" },
    });
  });

  it("§2.12 — parses stacked modifiers, important, and opacity", () => {
    expect(parsed("dark:hover:!bg-red-500/50")).toMatchObject({
      utility: "bg",
      modifiers: [
        { raw: "dark", kind: "dark" },
        { raw: "hover", kind: "state" },
      ],
      important: true,
      negative: false,
      prefix: null,
      value: { kind: "token", token: "red-500", opacity: "50" },
    });
  });

  it("§2.13 — parses negative utilities", () => {
    expect(parsed("-mx-4")).toMatchObject({
      utility: "mx",
      modifiers: [],
      important: false,
      negative: true,
      prefix: null,
      value: { kind: "token", token: "4" },
    });
  });

  it("§2.14 — parses v4 CSS-variable references", () => {
    expect(parsed("bg-(--my-color)")).toMatchObject({
      utility: "bg",
      modifiers: [],
      important: false,
      negative: false,
      prefix: null,
      value: { kind: "css-variable", ref: "--my-color" },
    });
  });

  it.each(["flex", "block", "sr-only", "truncate"])(
    "§2.15 — parses utility-only class %s",
    (raw) => {
      expect(parsed(raw)).toMatchObject({
        utility: raw,
        modifiers: [],
        value: { kind: "none" },
      });
    },
  );

  it("§2.16 — parses selector modifiers", () => {
    expect(parsed("[&>div]:p-4")).toMatchObject({
      utility: "p",
      modifiers: [{ raw: "[&>div]", kind: "selector" }],
      value: { kind: "token", token: "4" },
    });
  });
});

describe("parseClass — Tailwind v4 grammar (regression: no-false-unknown)", () => {
  it("recognizes standalone arbitrary properties", () => {
    expect(parsed("[--cell-radius:var(--radius-md)]")).toMatchObject({
      utility: "[arbitrary-property]",
      value: { kind: "arbitrary", raw: "--cell-radius:var(--radius-md)" },
    });
    expect(parsed("[mask-type:luminance]").utility).toBe("[arbitrary-property]");
  });

  it("strips trailing `!` important (v4 syntax)", () => {
    expect(parsed("text-red-500!")).toMatchObject({
      utility: "text",
      important: true,
      value: { kind: "token", token: "red-500" },
    });
    expect(parsed("shadow-none!")).toMatchObject({ utility: "shadow", important: true });
  });

  it("recognizes group/peer markers including named variants", () => {
    expect(parsed("group").utility).toBe("group");
    expect(parsed("group/avatar").utility).toBe("group");
    expect(parsed("peer").utility).toBe("peer");
    expect(parsed("peer/field-label").utility).toBe("peer");
  });

  it("recognizes the @container marker and named containers", () => {
    expect(parsed("@container").utility).toBe("@container");
    expect(parsed("@container/card-header").utility).toBe("@container");
  });

  it.each(["self-stretch", "self-start", "auto-rows-min", "field-sizing-content", "wrap-break-word"])(
    "recognizes core v4 utility %s",
    (raw) => {
      expect(parsed(raw).utility).not.toBe("unknown");
    },
  );

  it("still flags genuinely unknown classes", () => {
    expect(parsed("totally-made-up-utility").utility).toBe("unknown");
  });
});

describe("parseClass — Stage 1 integration", () => {
  it("parses every classname_literal class into one tailwind_class fact", () => {
    const facts = extractJsxFacts(
      `export default function P() {
        return <div className="m-4 bg-red-500">child</div>;
      }`,
      FILE,
    );

    const literal = facts.find((f) => f.kind === "classname_literal");
    const classes = facts.filter((f) => f.kind === "tailwind_class");

    expect(literal).toMatchObject({
      kind: "classname_literal",
      classes: ["m-4", "bg-red-500"],
    });
    expect(classes).toHaveLength(2);
    expect(classes).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          kind: "tailwind_class",
          raw: "m-4",
          utility: "m",
          value: { kind: "token", token: "4" },
        }),
        expect.objectContaining({
          kind: "tailwind_class",
          raw: "bg-red-500",
          utility: "bg",
          value: { kind: "token", token: "red-500" },
        }),
      ]),
    );
  });

  it("keeps repeated classes as distinct facts", () => {
    const facts = extractJsxFacts(
      `export default function P() {
        return <div className="p-4 p-4">child</div>;
      }`,
      FILE,
    ).filter((f) => f.kind === "tailwind_class");

    expect(facts).toHaveLength(2);
    expect(new Set(facts.map((f) => f.id)).size).toBe(2);
  });
});
