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

import { aggregateComponentUsage } from "../aggregate.js";
import { extractJsxFacts } from "../jsx.js";

const FILE = "apps/checkout/page.tsx";

describe("aggregateComponentUsage", () => {
  it("keeps aliased JSX element names separate from component identity", () => {
    const facts = extractJsxFacts(
      `import { Button as FuiButton } from "@fragments-sdk/ui";

export function Page() {
  return <FuiButton variant="primary" />;
}
`,
      FILE,
    );

    const analysis = aggregateComponentUsage(facts);

    expect(analysis.callSites).toEqual([
      expect.objectContaining({
        componentId: "@fragments-sdk/ui#Button",
        element: "FuiButton",
        props: [
          expect.objectContaining({
            name: "variant",
            kind: "static",
            value: "primary",
          }),
        ],
      }),
    ]);
    expect(analysis.summaries).toEqual([
      expect.objectContaining({
        componentId: "@fragments-sdk/ui#Button",
        callSiteCount: 1,
        propCounts: {
          variant: 1,
        },
      }),
    ]);
  });

  it("groups resolved JSX facts into component call sites and summaries", () => {
    const facts = extractJsxFacts(
      `import { Button } from "@fragments-sdk/ui";

export function Page({ disabled, extras }: any) {
  return (
    <>
      <Button variant="secondary" disabled={disabled} />
      <Button variant="primary" {...extras}>Save</Button>
      <Button>{disabled ? "Saving" : "Save"}</Button>
    </>
  );
}
`,
      FILE,
    );

    const analysis = aggregateComponentUsage(facts);

    expect(analysis.callSites).toHaveLength(3);
    expect(analysis.callSites[0]).toMatchObject({
      componentId: "@fragments-sdk/ui#Button",
      element: "Button",
      filePath: FILE,
      props: [
        {
          name: "variant",
          kind: "static",
          value: "secondary",
        },
        {
          name: "disabled",
          kind: "dynamic",
        },
      ],
    });
    expect(analysis.callSites[0].props[0].location?.line).toBe(6);
    expect(analysis.callSites[1].textChildren).toEqual(["Save"]);
    expect(analysis.callSites[2].props).toEqual([
      expect.objectContaining({
        name: "children",
        kind: "jsx",
      }),
    ]);

    expect(analysis.summaries).toEqual([
      expect.objectContaining({
        componentId: "@fragments-sdk/ui#Button",
        callSiteCount: 3,
        importingFileCount: 1,
        propCounts: {
          children: 1,
          disabled: 1,
          variant: 2,
        },
        staticValueCounts: {
          variant: {
            primary: 1,
            secondary: 1,
          },
        },
        dynamicPropCounts: {
          disabled: 1,
        },
        spreadCount: 1,
      }),
    ]);
  });

  it("keeps compound member usage separate from the root component", () => {
    const facts = extractJsxFacts(
      `import { Card } from "@fragments-sdk/ui";

export function Page() {
  return (
    <Card>
      <Card.Header tone="muted">Checkout</Card.Header>
    </Card>
  );
}
`,
      FILE,
    );

    const analysis = aggregateComponentUsage(facts);

    expect(analysis.callSites.map((site) => site.componentId)).toEqual([
      "@fragments-sdk/ui#Card",
      "@fragments-sdk/ui#Card.Header",
    ]);
    expect(analysis.callSites[1]).toMatchObject({
      componentId: "@fragments-sdk/ui#Card.Header",
      element: "Card.Header",
      props: [
        {
          name: "tone",
          kind: "static",
          value: "muted",
        },
      ],
      textChildren: ["Checkout"],
    });

    expect(analysis.summaries).toEqual([
      expect.objectContaining({
        componentId: "@fragments-sdk/ui#Card",
        callSiteCount: 1,
        propCounts: {
          children: 1,
        },
      }),
      expect.objectContaining({
        componentId: "@fragments-sdk/ui#Card.Header",
        callSiteCount: 1,
        propCounts: {
          tone: 1,
        },
        staticValueCounts: {
          tone: {
            muted: 1,
          },
        },
      }),
    ]);
  });

  it("aggregates relative usage resolved by the caller", () => {
    const facts = extractJsxFacts(
      `import { Avatar } from "./components/Avatar";
import { Card } from "./components/Card";

export function ActivityFeed({ item }: any) {
  return (
    <Card>
      <Card.Body>
        <Avatar src={item.actor.avatarUrl} name={item.actor.name} size="sm" />
      </Card.Body>
    </Card>
  );
}
`,
      FILE,
      {
        resolveComponentId(resolution) {
          const imported = resolution.imported;
          if (resolution.source === "./components/Avatar") {
            return asComponentId(`local-design-system#${imported}`);
          }
          if (resolution.source === "./components/Card") {
            return asComponentId(`local-design-system#${imported}`);
          }
          return undefined;
        },
      },
    );

    const analysis = aggregateComponentUsage(facts);

    expect(analysis.callSites.map((site) => site.componentId)).toEqual([
      "local-design-system#Avatar",
      "local-design-system#Card",
      "local-design-system#Card.Body",
    ]);
    expect(analysis.callSites.map((site) => [site.element, site.line])).toEqual([
      ["Avatar", 8],
      ["Card", 6],
      ["Card.Body", 7],
    ]);
    expect(
      analysis.callSites.find(
        (site) => site.componentId === "local-design-system#Avatar",
      )?.props,
    ).toEqual([
      expect.objectContaining({ name: "src", kind: "dynamic" }),
      expect.objectContaining({ name: "name", kind: "dynamic" }),
      expect.objectContaining({ name: "size", kind: "static", value: "sm" }),
    ]);
    expect(
      analysis.callSites.find(
        (site) => site.componentId === "local-design-system#Card.Body",
      )?.props,
    ).toEqual([expect.objectContaining({ name: "children", kind: "jsx" })]);
  });

  it("aggregates direct compound aliases resolved by the caller", () => {
    const facts = extractJsxFacts(
      `import { AccordionItem, CardBody } from "@fragments-sdk/ui";

export function Page({ item }: any) {
  return (
    <>
      <CardBody tone="muted">Billing</CardBody>
      <AccordionItem value={item.id}>{item.label}</AccordionItem>
    </>
  );
}
`,
      FILE,
      {
        resolveComponentId(resolution) {
          if (resolution.imported === "CardBody") {
            return asComponentId("@fragments-sdk/ui#Card.Body");
          }
          if (resolution.imported === "AccordionItem") {
            return asComponentId("@fragments-sdk/ui#Accordion.Item");
          }
          return undefined;
        },
      },
    );

    const analysis = aggregateComponentUsage(facts);

    expect(analysis.callSites.map((site) => site.componentId)).toEqual([
      "@fragments-sdk/ui#Accordion.Item",
      "@fragments-sdk/ui#Card.Body",
    ]);
    expect(analysis.callSites.map((site) => site.element)).toEqual([
      "AccordionItem",
      "CardBody",
    ]);
    expect(analysis.callSites[0]).toMatchObject({
      componentId: "@fragments-sdk/ui#Accordion.Item",
      props: [
        { name: "value", kind: "dynamic" },
        { name: "children", kind: "jsx" },
      ],
    });
    expect(analysis.callSites[1]).toMatchObject({
      componentId: "@fragments-sdk/ui#Card.Body",
      props: [{ name: "tone", kind: "static", value: "muted" }],
      textChildren: ["Billing"],
    });
    expect(analysis.summaries).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          componentId: "@fragments-sdk/ui#Accordion.Item",
          propCounts: {
            children: 1,
            value: 1,
          },
          dynamicPropCounts: {
            value: 1,
          },
        }),
        expect.objectContaining({
          componentId: "@fragments-sdk/ui#Card.Body",
          propCounts: {
            tone: 1,
          },
          staticValueCounts: {
            tone: {
              muted: 1,
            },
          },
        }),
      ]),
    );
  });

  it("summarizes expression props as dynamic without static values", () => {
    const facts = extractJsxFacts(
      `import { Button } from "@fragments-sdk/ui";

export function Page({ tone, settings, label }: any) {
  return (
    <Button
      tone={tone}
      size={settings.button.size}
      label={\`Pay \${label}\`}
    />
  );
}
`,
      FILE,
    );

    const analysis = aggregateComponentUsage(facts);

    expect(analysis.callSites).toHaveLength(1);
    expect(analysis.callSites[0].props).toEqual([
      expect.objectContaining({ name: "tone", kind: "dynamic" }),
      expect.objectContaining({ name: "size", kind: "dynamic" }),
      expect.objectContaining({ name: "label", kind: "dynamic" }),
    ]);
    expect(analysis.callSites[0].props.map((prop) => prop.value)).toEqual([
      undefined,
      undefined,
      undefined,
    ]);
    expect(analysis.summaries).toEqual([
      expect.objectContaining({
        componentId: "@fragments-sdk/ui#Button",
        dynamicPropCounts: {
          label: 1,
          size: 1,
          tone: 1,
        },
        staticValueCounts: {},
      }),
    ]);
  });

  it("keeps map and conditional component call sites distinct with stable locations", () => {
    const facts = extractJsxFacts(
      `import { Button } from "@fragments-sdk/ui";

export function Page({ items, show }: any) {
  return (
    <section>
      {items.map((item: any) => (
        <Button key={item.id} variant={item.variant}>{item.label}</Button>
      ))}
      {show ? <Button variant="primary">Confirm</Button> : null}
    </section>
  );
}
`,
      FILE,
    );

    const analysis = aggregateComponentUsage(facts);
    const buttonSites = analysis.callSites.filter(
      (site) => site.componentId === "@fragments-sdk/ui#Button",
    );
    const nodePaths = new Set(buttonSites.map((site) => site.nodePath));

    expect(buttonSites).toHaveLength(2);
    expect(nodePaths.size).toBe(buttonSites.length);
    expect(buttonSites.map((site) => [site.filePath, site.line])).toEqual([
      [FILE, 7],
      [FILE, 9],
    ]);
    expect(buttonSites[0].props).toEqual([
      expect.objectContaining({ name: "variant", kind: "dynamic" }),
      expect.objectContaining({ name: "children", kind: "jsx" }),
    ]);
    expect(buttonSites[1].props).toEqual([
      expect.objectContaining({
        name: "variant",
        kind: "static",
        value: "primary",
      }),
    ]);
    expect(buttonSites[1].textChildren).toEqual(["Confirm"]);
  });

  it("aggregates Omlet-style component usage patterns", () => {
    const facts = extractJsxFacts(
      `import { Accordion, Avatar, Button, Card } from "@fragments-sdk/ui";

export function UsageSurvey({ busy, extras, items, person }: any) {
  return (
    <main>
      <Button variant="primary" disabled={busy} count={3} />
      <Button {...extras}>{busy ? "Saving" : "Save"}</Button>
      <Avatar src={person.avatarUrl} name={person.name} size="sm" />
      <Avatar.Group size="sm" max={5}>
        <img alt={person.name} />
      </Avatar.Group>
      <Card.Body padded={null}>
        <p>{person.name}</p>
      </Card.Body>
      <Accordion.Root type="single">
        {items.map((item: any) => (
          <Accordion.Item key={item.id} value={item.id}>
            {item.label}
          </Accordion.Item>
        ))}
      </Accordion.Root>
    </main>
  );
}
`,
      FILE,
    );

    const analysis = aggregateComponentUsage(facts);
    const byComponent = new Map(
      analysis.callSites.map((site) => [String(site.componentId), site]),
    );
    const buttonSites = analysis.callSites.filter(
      (site) => site.componentId === "@fragments-sdk/ui#Button",
    );

    expect([...byComponent.keys()].sort()).toEqual([
      "@fragments-sdk/ui#Accordion.Item",
      "@fragments-sdk/ui#Accordion.Root",
      "@fragments-sdk/ui#Avatar",
      "@fragments-sdk/ui#Avatar.Group",
      "@fragments-sdk/ui#Button",
      "@fragments-sdk/ui#Card.Body",
    ]);

    expect(buttonSites).toHaveLength(2);
    expect(buttonSites.map((site) => [site.element, site.line])).toEqual([
      ["Button", 6],
      ["Button", 7],
    ]);
    expect(buttonSites[0].props).toEqual([
      expect.objectContaining({
        name: "variant",
        kind: "static",
        value: "primary",
      }),
      expect.objectContaining({ name: "disabled", kind: "dynamic" }),
      expect.objectContaining({ name: "count", kind: "static", value: 3 }),
    ]);
    expect(buttonSites[1].props).toEqual([
      expect.objectContaining({ name: "...extras", kind: "spread" }),
      expect.objectContaining({ name: "children", kind: "jsx" }),
    ]);

    expect(byComponent.get("@fragments-sdk/ui#Avatar")).toMatchObject({
      element: "Avatar",
      line: 8,
      props: [
        { name: "src", kind: "dynamic" },
        { name: "name", kind: "dynamic" },
        { name: "size", kind: "static", value: "sm" },
      ],
    });
    expect(byComponent.get("@fragments-sdk/ui#Avatar.Group")).toMatchObject({
      element: "Avatar.Group",
      line: 9,
      props: [
        { name: "size", kind: "static", value: "sm" },
        { name: "max", kind: "static", value: 5 },
        { name: "children", kind: "jsx" },
      ],
    });
    expect(byComponent.get("@fragments-sdk/ui#Card.Body")).toMatchObject({
      element: "Card.Body",
      line: 12,
      props: [
        { name: "padded", kind: "null", value: null },
        { name: "children", kind: "jsx" },
      ],
    });
    expect(byComponent.get("@fragments-sdk/ui#Accordion.Item")).toMatchObject({
      element: "Accordion.Item",
      line: 17,
      props: [
        { name: "value", kind: "dynamic" },
        { name: "children", kind: "jsx" },
      ],
    });

    expect(analysis.summaries).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          componentId: "@fragments-sdk/ui#Button",
          callSiteCount: 2,
          propCounts: {
            children: 1,
            count: 1,
            disabled: 1,
            variant: 1,
          },
          dynamicPropCounts: {
            disabled: 1,
          },
          staticValueCounts: {
            count: {
              "3": 1,
            },
            variant: {
              primary: 1,
            },
          },
          spreadCount: 1,
        }),
        expect.objectContaining({
          componentId: "@fragments-sdk/ui#Accordion.Item",
          propCounts: {
            children: 1,
            value: 1,
          },
          dynamicPropCounts: {
            value: 1,
          },
        }),
      ]),
    );
  });
});
