import { describe, it, expect } from "vitest";
import {
  parseStoryContent,
  parseStoryFacts,
  parseStorybookConfigContent,
  extractCategory,
  storyNameToTitle,
  isStoryFilePath,
  storyFileBasename,
  DEFAULT_STORY_PATTERNS,
} from "../index.js";

describe("parseStoryFacts", () => {
  it("captures the canonical CSF3 StoryFacts fixture", () => {
    const source = `
      import type { Meta, StoryObj } from "@storybook/react";
      import { Button } from "./Button";

      const meta: Meta<typeof Button> = {
        title: "Primitives/Button",
        component: Button,
        tags: ["autodocs"],
        args: { size: "md" },
        argTypes: {
          variant: { options: ["primary", "secondary", "ghost"] },
        },
      };
      export default meta;
      type Story = StoryObj<typeof Button>;

      export const Primary: Story = { args: { variant: "primary" } };
      export const Secondary: Story = { args: { variant: "secondary" } };
      export const Disabled: Story = {
        args: { variant: "primary", disabled: true },
      };
    `;

    const facts = parseStoryFacts(source, "src/ui/Button.stories.tsx");

    expect(facts).toMatchObject({
      file: "src/ui/Button.stories.tsx",
      title: "Primitives/Button",
      componentImport: { specifier: "./Button", name: "Button" },
      tags: ["autodocs"],
      metaArgs: { size: "md" },
      argTypes: {
        variant: { options: ["primary", "secondary", "ghost"] },
      },
      rung: "full",
    });
    expect(facts.stories).toEqual([
      {
        ref: "src/ui/Button.stories.tsx#Primary",
        name: "Primary",
        args: { variant: "primary" },
      },
      {
        ref: "src/ui/Button.stories.tsx#Secondary",
        name: "Secondary",
        args: { variant: "secondary" },
      },
      {
        ref: "src/ui/Button.stories.tsx#Disabled",
        name: "Disabled",
        args: { variant: "primary", disabled: true },
      },
    ]);
    expect(facts.diagnostics).toEqual([]);
  });

  it("records dynamic stories without fabricating helper-derived args", () => {
    const source = `
      import type { StoryObj } from "@storybook/react";
      import { Notice } from "./Notice";

      type Props = { tone: "loud" | "quiet"; size: "md" };
      const base = { size: "md" };
      const quietPreset = { tone: "quiet" };
      const meta = { title: "Feedback/Notice", component: Notice };
      export default meta;
      type Story = StoryObj<typeof Notice>;
      const make = (overrides: Partial<Props>): Story => ({
        args: { ...base, ...overrides },
      });

      export const Loud = make({ tone: "loud" });
      export const Quiet = make(quietPreset);
    `;

    const facts = parseStoryFacts(source, "src/Notice.stories.tsx");

    expect(facts.rung).toBe("partial");
    expect(facts.stories).toEqual([
      {
        ref: "src/Notice.stories.tsx#Loud",
        name: "Loud",
        args: { tone: "loud" },
      },
      { ref: "src/Notice.stories.tsx#Quiet", name: "Quiet" },
    ]);
  });

  it("returns a skipped rung for syntax errors", () => {
    const facts = parseStoryFacts("const meta = { title: 'Broken'", "src/Broken.stories.tsx");

    expect(facts.rung).toBe("skipped");
    expect(facts.stories).toEqual([]);
    expect(facts.diagnostics[0]).toMatchObject({ level: "error" });
  });
});

describe("parseStoryContent (CSF 3.0)", () => {
  const source = `
    import type { Meta, StoryObj } from "@storybook/react";
    import { Button } from "@fragments-sdk/ui";

    const meta = {
      title: "Atoms/Button",
      component: Button,
      tags: ["autodocs"],
      argTypes: {
        variant: {
          control: "select",
          options: ["primary", "secondary", "ghost"],
          description: "Visual style",
        },
        disabled: { control: "boolean" },
      },
    } satisfies Meta<typeof Button>;
    export default meta;

    type Story = StoryObj<typeof meta>;

    export const Primary: Story = { args: { variant: "primary", children: "Save" } };
    export const Ghost: Story = { args: { variant: "ghost", disabled: true } };
  `;

  it("extracts the component name and import path", () => {
    const parsed = parseStoryContent(source, "Button.stories.tsx");
    expect(parsed.meta.componentName).toBe("Button");
    expect(parsed.meta.componentImport).toBe("@fragments-sdk/ui");
    expect(parsed.meta.title).toBe("Atoms/Button");
    expect(parsed.meta.tags).toEqual(["autodocs"]);
  });

  it("extracts argTypes with options as variant signal", () => {
    const parsed = parseStoryContent(source, "Button.stories.tsx");
    expect(parsed.argTypes.variant?.control).toBe("select");
    expect(parsed.argTypes.variant?.options).toEqual(["primary", "secondary", "ghost"]);
    expect(parsed.argTypes.disabled?.control).toBe("boolean");
  });

  it("extracts each story export with its args", () => {
    const parsed = parseStoryContent(source, "Button.stories.tsx");
    const names = parsed.stories.map((s) => s.name).sort();
    expect(names).toEqual(["Ghost", "Primary"]);
    const primary = parsed.stories.find((s) => s.name === "Primary");
    expect(primary?.args.variant).toBe("primary");
    expect(primary?.args.children).toBe("Save");
    const ghost = parsed.stories.find((s) => s.name === "Ghost");
    expect(ghost?.args.disabled).toBe(true);
  });

  it("reports high confidence for a clean file", () => {
    const parsed = parseStoryContent(source, "Button.stories.tsx");
    expect(parsed.confidence).toBeGreaterThanOrEqual(0.8);
  });

  it("does not mistake args.title for the Storybook meta title", () => {
    const parsed = parseStoryContent(
      `
        import type { Meta, StoryObj } from "@storybook/react-vite";
        import Card from ".";

        const meta: Meta<typeof Card> = {
          component: Card,
          args: {
            title: "Hello",
            children: "This is some text",
          },
        };

        export default meta;
        type Story = StoryObj<typeof Card>;
        export const Primary: Story = {
          render: (args) => <Card {...args} />,
        };
      `,
      "src/components/Card/Card.stories.tsx"
    );

    expect(parsed.meta.componentName).toBe("Card");
    expect(parsed.meta.title).toBe("Components/Card");
    expect(parsed.stories[0].args).toEqual({});
  });
});

describe("parseStoryContent (CSF 2.0)", () => {
  it("extracts Template.bind stories and their .args", () => {
    const source = `
      import { Badge } from "@fragments-sdk/ui";
      export default { title: "Atoms/Badge", component: Badge };
      const Template = (args) => <Badge {...args} />;
      export const Default = Template.bind({});
      Default.args = { tone: "info", children: "New" };
    `;
    const parsed = parseStoryContent(source, "Badge.stories.tsx");
    expect(parsed.meta.componentName).toBe("Badge");
    expect(parsed.meta.title).toBe("Atoms/Badge");
    const story = parsed.stories.find((s) => s.name === "Default");
    expect(story?.args.tone).toBe("info");
    expect(story?.hasCustomRender).toBe(false);
  });

  it("extracts meta from a named default export that is not called meta", () => {
    const source = `
      import { Badge } from "@fragments-sdk/ui";
      const storyConfig = { title: "Atoms/Badge", component: Badge };
      export default storyConfig;
      export const Default = {};
    `;
    const parsed = parseStoryContent(source, "Badge.stories.tsx");

    expect(parsed.meta.componentName).toBe("Badge");
    expect(parsed.meta.title).toBe("Atoms/Badge");
  });
});

describe("extractCategory / storyNameToTitle", () => {
  it("derives the atomic-design tier from the title path", () => {
    expect(extractCategory("Atoms/Button")).toBe("atoms");
    expect(extractCategory("Components/Forms/Input")).toBe("forms");
    expect(extractCategory("Button")).toBe("components");
  });

  it("humanizes story export names", () => {
    expect(storyNameToTitle("PrimaryButton")).toBe("Primary Button");
    expect(storyNameToTitle("DisabledState")).toBe("Disabled State");
  });
});

describe("parseStorybookConfigContent", () => {
  it("extracts story globs, framework and builder", () => {
    const config = `
      import type { StorybookConfig } from "@storybook/react-vite";
      const config: StorybookConfig = {
        stories: ["../src/**/*.stories.@(ts|tsx)"],
        framework: { name: "@storybook/react-vite" },
        core: { builder: "vite" },
      };
      export default config;
    `;
    const parsed = parseStorybookConfigContent(config, ".storybook/main.ts");
    expect(parsed.storyPatterns).toEqual(["../src/**/*.stories.@(ts|tsx)"]);
    expect(parsed.framework).toBe("@storybook/react-vite");
    expect(parsed.builder).toBe("vite");
    expect(parsed.valid).toBe(true);
  });

  it("extracts object-form story globs without splitting object fields", () => {
    const config = `
      export default {
        stories: [
          { directory: "../src", files: "*.mdx" },
          { directory: "../src", files: "**/*.stories.@(ts|tsx)" },
        ],
      };
    `;
    const parsed = parseStorybookConfigContent(config, ".storybook/main.ts");

    expect(parsed.storyPatterns).toEqual(["../src/*.mdx", "../src/**/*.stories.@(ts|tsx)"]);
  });

  it("falls back to default patterns when none are declared", () => {
    const parsed = parseStorybookConfigContent("export default {};", ".storybook/main.ts");
    expect(parsed.storyPatterns).toEqual(DEFAULT_STORY_PATTERNS);
  });
});

describe("isStoryFilePath / storyFileBasename", () => {
  it("matches story files and rejects tests, node_modules, and non-stories", () => {
    expect(isStoryFilePath("src/components/Button/Button.stories.tsx")).toBe(true);
    expect(isStoryFilePath("src/components/Card/Card.story.jsx")).toBe(true);
    expect(isStoryFilePath("src/components/Button/Button.stories.test.tsx")).toBe(false);
    expect(isStoryFilePath("node_modules/foo/Foo.stories.tsx")).toBe(false);
    expect(isStoryFilePath("src/components/Button/Button.tsx")).toBe(false);
  });

  it("recovers the documented component basename", () => {
    expect(storyFileBasename("src/components/Button/Button.stories.tsx")).toBe("Button");
    expect(storyFileBasename("src/components/Button/Button.tsx")).toBeNull();
  });
});
