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

import { createMemoryScanSource } from "../source/memory-source.js";
import type { UniversalComponentFact } from "../ucf/types.js";
import { analyzeReactComponentAdoption } from "./component-adoption.js";

describe("analyzeReactComponentAdoption", () => {
  it("resolves direct and aliased relative imports", async () => {
    const files = {
      "src/Button.tsx": [
        "export function Button() {",
        "  return <button>Go</button>;",
        "}",
      ].join("\n"),
      "src/page.tsx": [
        "import { Button as FuiButton } from './Button';",
        "export function Page() {",
        "  return <FuiButton variant=\"primary\" disabled />;",
        "}",
      ].join("\n"),
    };

    const analysis = await analyze(files, [
      component("Button", "src/Button.tsx"),
    ]);
    const button = componentAnalysis(analysis, "Button");

    expect(button).toMatchObject({
      totalImporters: 1,
      totalCallSites: 1,
      confidence: "confirmed",
    });
    expect(button.topImporters).toEqual([
      expect.objectContaining({ filePath: "src/page.tsx" }),
    ]);
    expect(button.callSites).toEqual([
      expect.objectContaining({
        filePath: "src/page.tsx",
        props: expect.arrayContaining([
          { name: "disabled", kind: "boolean", value: "true" },
          { name: "variant", kind: "static", value: "primary" },
        ]),
      }),
    ]);
    expect(analysis.unresolvedImports).toEqual([]);
  });

  it("resolves compound member and implementation aliases", async () => {
    const files = {
      "src/Card.tsx": [
        "function CardRoot() { return <section />; }",
        "function CardBody({ tone }: { tone?: 'muted' | 'default' }) {",
        "  return <div data-tone={tone} />;",
        "}",
        "export const Card = Object.assign(CardRoot, { Body: CardBody });",
        "export { CardBody };",
      ].join("\n"),
      "src/page.tsx": [
        "import { Card, CardBody } from './Card';",
        "export function Page() {",
        "  return (",
        "    <>",
        "      <Card.Body tone=\"default\" />",
        "      <CardBody tone=\"muted\" />",
        "    </>",
        "  );",
        "}",
      ].join("\n"),
    };

    const analysis = await analyze(files, [
      component("Card", "src/Card.tsx", {
        exports: ["Card"],
        compoundChildren: ["Body"],
      }),
      component("CardBody", "src/Card.tsx", { exports: ["CardBody"] }),
    ]);
    const body = componentAnalysis(analysis, "Card.Body");

    expect(body.totalCallSites).toBe(2);
    expect(body.callSites).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          props: [{ name: "tone", kind: "static", value: "default" }],
        }),
        expect.objectContaining({
          props: [{ name: "tone", kind: "static", value: "muted" }],
        }),
      ]),
    );
  });

  it("attributes same-file component graph edges to the owning component", async () => {
    const files = {
      "src/Card.tsx": [
        "export function Card() {",
        "  return <section />;",
        "}",
      ].join("\n"),
      "src/Field.tsx": [
        "import { Card } from './Card';",
        "export function Field() {",
        "  return <Card />;",
        "}",
        "export function Other() {",
        "  return <span />;",
        "}",
      ].join("\n"),
    };

    const analysis = await analyze(files, [
      component("Card", "src/Card.tsx"),
      component("Field", "src/Field.tsx"),
      component("Other", "src/Field.tsx"),
    ]);
    const card = componentAnalysis(analysis, "Card");
    const field = componentAnalysis(analysis, "Field");

    expect(card.usedBy.map((ref) => ref.componentName)).toEqual(["Field"]);
    expect(field.uses.map((ref) => ref.componentName)).toEqual(["Card"]);
    expect(card.usedBy.map((ref) => ref.componentName)).not.toContain("Other");
  });

  it("reports unresolved local imports without inventing adoption", async () => {
    const files = {
      "src/page.tsx": [
        "import { Missing } from './missing';",
        "export function Page() {",
        "  return <Missing />;",
        "}",
      ].join("\n"),
    };

    const analysis = await analyze(files, []);

    expect(analysis.components).toEqual([]);
    expect(analysis.unresolvedImports).toEqual([
      expect.objectContaining({
        filePath: "src/page.tsx",
        elementName: "Missing",
        local: "Missing",
        imported: "Missing",
        source: "./missing",
        reason: "unresolved",
      }),
    ]);
  });
});

async function analyze(
  files: Record<string, string>,
  ucfs: UniversalComponentFact[],
) {
  return analyzeReactComponentAdoption({
    source: createMemoryScanSource(files),
    files: Object.entries(files).map(([relPath, content]) => ({
      relPath,
      size: content.length,
    })),
    ucfs,
    createIdentity(input) {
      const componentKey = `${input.filePath}::${input.componentName}`;
      return {
        componentKey,
        componentId: asComponentId(`test#${componentKey}`),
      };
    },
  });
}

function componentAnalysis(
  analysis: Awaited<ReturnType<typeof analyze>>,
  componentName: string,
) {
  const component = analysis.components.find(
    (candidate) => candidate.componentName === componentName,
  );
  if (!component) throw new Error(`Missing component ${componentName}`);
  return component;
}

function component(
  componentName: string,
  filePath: string,
  options: {
    exports?: string[];
    compoundChildren?: string[];
  } = {},
): UniversalComponentFact {
  const exports = options.exports ?? [componentName];
  const loc: FactLocation = { file: filePath, line: 1, column: 0 };
  return {
    id: `${filePath}#${componentName}`,
    filePath,
    componentName,
    framework: "react",
    sourceCommit: "sha",
    capturedAt: "2026-05-26T00:00:00.000Z",
    adapterVersion: "test",
    definitionKind: "function",
    wrappedBy: [],
    imports: [],
    exports: exports.map((exportedAs, index) => ({
      exportedAs,
      isPrimary: index === 0,
      localName: componentName,
      isDefault: exportedAs === "default",
    })),
    exportedFromBarrel: false,
    rootElements: [{ tag: "div", attrs: {}, loc }],
    rootElementsTruncated: false,
    ariaRoles: [],
    ariaAttributes: {},
    props: [],
    events: [],
    slots: [],
    hasInternalState: false,
    hasEffects: false,
    isControlled: "unknown",
    compoundChildren: options.compoundChildren ?? [],
    styleSystem: "unknown",
    classNamesUsed: [],
  };
}
