import { describe, expect, it } from "vitest";
import type { GovernanceConfig } from "@fragments-sdk/core";

import { scanWithFacts } from "../typestyle/check.js";
import { extractCssInJsFacts } from "./css-in-js.js";

const rawColorPolicy: GovernanceConfig = {
  styles: [
    {
      kind: "style.rawColors.forbid",
      except: ["transparent", "currentColor", "inherit"],
      prefer: "token",
      severity: "error",
    },
  ],
};

describe("extractCssInJsFacts", () => {
  it("extracts static styled-components declarations as style facts", () => {
    const facts = extractCssInJsFacts(
      [
        "import styled from 'styled-components';",
        "export const Button = styled.button`",
        "  color: #2563eb;",
        "  padding: 12px;",
        "`;",
      ].join("\n"),
      "src/Button.tsx",
    );

    expect(
      facts.map((fact) =>
        fact.kind === "style_declaration"
          ? { property: fact.property, value: fact.value, line: fact.location.line }
          : null,
      ),
    ).toEqual([
      { property: "color", value: "#2563eb", line: 3 },
      { property: "padding", value: "12px", line: 4 },
    ]);
  });

  it("feeds static emotion css declarations into governance rules", () => {
    const result = scanWithFacts({
      facts: { governConfig: rawColorPolicy },
      files: [
        {
          absPath: "/repo/src/Card.tsx",
          relPath: "src/Card.tsx",
          content: [
            "import { css } from '@emotion/react';",
            "export const card = css`",
            "  color: #2563eb;",
            "`;",
          ].join("\n"),
        },
      ],
    });

    expect(result.findings).toEqual([
      expect.objectContaining({
        ruleId: "styles/no-raw-color",
        location: expect.objectContaining({ file: "src/Card.tsx", line: 3 }),
        attributes: expect.objectContaining({ rawValue: "#2563eb" }),
      }),
    ]);
  });

  it("extracts static object styles from styled and emotion calls", () => {
    const facts = extractCssInJsFacts(
      [
        "import styled from 'styled-components';",
        "import { css } from '@emotion/react';",
        "export const Button = styled.button({",
        "  backgroundColor: '#2563eb',",
        "  '&:hover': { color: '#1d4ed8' },",
        "});",
        "export const panel = css({ borderColor: `#0f172a` });",
      ].join("\n"),
      "src/Button.tsx",
    );

    expect(
      facts.map((fact) =>
        fact.kind === "style_declaration"
          ? {
              property: fact.property,
              value: fact.value,
              selector: fact.selector,
              line: fact.location.line,
            }
          : null,
      ),
    ).toEqual([
      {
        property: "background-color",
        value: "#2563eb",
        selector: "css-in-js:styled.button:3:",
        line: 4,
      },
      {
        property: "color",
        value: "#1d4ed8",
        selector: "css-in-js:styled.button:3:&:hover",
        line: 5,
      },
      {
        property: "border-color",
        value: "#0f172a",
        selector: "css-in-js:css:7:",
        line: 7,
      },
    ]);
  });

  it("feeds static css prop object declarations into governance rules", () => {
    const result = scanWithFacts({
      facts: { governConfig: rawColorPolicy },
      files: [
        {
          absPath: "/repo/src/Card.tsx",
          relPath: "src/Card.tsx",
          content: [
            "export function Card() {",
            "  return <div css={{ color: '#2563eb' }} />;",
            "}",
          ].join("\n"),
        },
      ],
    });

    expect(result.findings).toEqual([
      expect.objectContaining({
        ruleId: "styles/no-raw-color",
        location: expect.objectContaining({ file: "src/Card.tsx", line: 2 }),
        attributes: expect.objectContaining({
          property: "color",
          rawValue: "#2563eb",
        }),
      }),
    ]);
  });

  it("extracts styled attrs and withConfig template declarations", () => {
    const facts = extractCssInJsFacts(
      [
        "import styled from 'styled-components';",
        "export const Link = styled('a').attrs({ role: 'link' })`",
        "  color: #2563eb;",
        "`;",
        "export const Button = styled.button.withConfig({",
        "  shouldForwardProp: () => true,",
        "})`",
        "  background-color: #1d4ed8;",
        "`;",
      ].join("\n"),
      "src/Button.tsx",
    );

    expect(
      facts
        .filter((fact) => fact.kind === "style_declaration")
        .map((fact) => ({
          property: fact.property,
          value: fact.value,
          selector: fact.selector,
          line: fact.location.line,
        })),
    ).toEqual([
      {
        property: "color",
        value: "#2563eb",
        selector: "css-in-js:styled().attrs():2:",
        line: 3,
      },
      {
        property: "background-color",
        value: "#1d4ed8",
        selector: "css-in-js:styled.button.withConfig():5:",
        line: 8,
      },
    ]);
  });

  it("extracts static styles from arrays and reports dynamic array entries", () => {
    const facts = extractCssInJsFacts(
      [
        "import { css } from '@emotion/react';",
        "const base = css([",
        "  { color: '#2563eb' },",
        "  condition && { color: '#dc2626' },",
        "  { padding: 12 },",
        "]);",
      ].join("\n"),
      "src/Card.tsx",
    );

    expect(
      facts
        .filter((fact) => fact.kind === "style_declaration")
        .map((fact) => ({
          property: fact.property,
          value: fact.value,
          declarationPath: fact.declarationPath,
        })),
    ).toEqual([
      { property: "color", value: "#2563eb", declarationPath: "array/0/0" },
      { property: "padding", value: "12", declarationPath: "array/2/0" },
    ]);
    expect(
      facts
        .filter((fact) => fact.kind === "style_unsupported")
        .map((fact) => ({
          reason: fact.reason,
          syntax: fact.syntax,
          declarationPath: fact.declarationPath,
        })),
    ).toEqual([
      {
        reason: "unsupported-array-element",
        syntax: "css-object",
        declarationPath: "array/1",
      },
    ]);
  });

  it("extracts static sx prop declarations and reports dynamic sx values", () => {
    const facts = extractCssInJsFacts(
      [
        "export function Card({ tone }: { tone: string }) {",
        "  return <Box sx={{ color: '#2563eb', padding: tone }} />;",
        "}",
      ].join("\n"),
      "src/Card.tsx",
    );

    expect(
      facts
        .filter((fact) => fact.kind === "style_declaration")
        .map((fact) => ({
          property: fact.property,
          value: fact.value,
          selector: fact.selector,
        })),
    ).toEqual([
      {
        property: "color",
        value: "#2563eb",
        selector: "css-in-js:sx-prop:2:",
      },
    ]);
    expect(
      facts
        .filter((fact) => fact.kind === "style_unsupported")
        .map((fact) => ({
          property: fact.property,
          reason: fact.reason,
          syntax: fact.syntax,
        })),
    ).toEqual([
      {
        property: "padding",
        reason: "dynamic-object-value",
        syntax: "sx-prop",
      },
    ]);
  });

  it("supports same-file static const template interpolation", () => {
    const result = scanWithFacts({
      facts: { governConfig: rawColorPolicy },
      files: [
        {
          absPath: "/repo/src/Card.tsx",
          relPath: "src/Card.tsx",
          content: [
            "import styled from 'styled-components';",
            "const brand = '#2563eb';",
            "export const Card = styled.div`",
            "  color: ${brand};",
            "`;",
          ].join("\n"),
        },
      ],
    });

    expect(result.findings).toEqual([
      expect.objectContaining({
        ruleId: "styles/no-raw-color",
        location: expect.objectContaining({ file: "src/Card.tsx", line: 4 }),
        attributes: expect.objectContaining({ rawValue: "#2563eb" }),
      }),
    ]);
  });

  it("reports dynamic template expressions without strict findings", () => {
    const content = [
      "import styled from 'styled-components';",
      "export const Card = styled.div`",
      "  color: ${({ theme }) => theme.colors.brand};",
      "`;",
    ].join("\n");
    const facts = extractCssInJsFacts(content, "src/Card.tsx");
    const result = scanWithFacts({
      facts: { governConfig: rawColorPolicy },
      files: [{ absPath: "/repo/src/Card.tsx", relPath: "src/Card.tsx", content }],
    });

    expect(result.findings).toEqual([]);
    expect(
      facts
        .filter((fact) => fact.kind === "style_unsupported")
        .map((fact) => ({
          reason: fact.reason,
          syntax: fact.syntax,
          line: fact.location.line,
        })),
    ).toEqual([
      {
        reason: "dynamic-template-expression",
        syntax: "styled-tag",
        line: 3,
      },
    ]);
  });

  it("extracts static vanilla-extract and Stitches object calls", () => {
    const facts = extractCssInJsFacts(
      [
        "import { style } from '@vanilla-extract/css';",
        "import { css } from '@stitches/react';",
        "export const root = style({ color: '#2563eb' });",
        "export const button = css({ backgroundColor: '#1d4ed8' });",
      ].join("\n"),
      "src/styles.ts",
    );

    expect(
      facts
        .filter((fact) => fact.kind === "style_declaration")
        .map((fact) => ({
          property: fact.property,
          value: fact.value,
          syntax: fact.selector,
        })),
    ).toEqual([
      {
        property: "color",
        value: "#2563eb",
        syntax: "css-in-js:style:3:",
      },
      {
        property: "background-color",
        value: "#1d4ed8",
        syntax: "css-in-js:css:4:",
      },
    ]);
  });
});
