import { describe, it, expect } from "vitest";
import type { PlopTypes } from "@turbo/gen";
import generator from "./config.js";

describe("core-package generator", () => {
  it("is registered with realtime and events in choices list", () => {
    const captured: Array<{ name: string; def: PlopTypes.PlopGeneratorConfig }> = [];
    const plopMock = {
      setHelper: () => {},
      setGenerator: (name: string, def: PlopTypes.PlopGeneratorConfig) =>
        captured.push({ name, def }),
    } as unknown as PlopTypes.NodePlopAPI;
    generator(plopMock);
    const corePkg = captured.find((c) => c.name === "core-package");
    expect(corePkg).toBeDefined();
    const prompts = corePkg!.def.prompts as Array<{ name: string; choices: unknown[] }>;
    expect(prompts[0]!.name).toBe("name");
    expect(prompts[0]!.choices).toContain("realtime");
    expect(prompts[0]!.choices).toContain("events");
    expect(prompts[0]!.choices).toContain("trpc");
    expect(prompts[0]!.choices).toContain("ui");
  });
});

describe("core-package realtime", () => {
  it("emits actions covering package files, transpilePackages, and ESLint rules", () => {
    // Capture the actions returned by the realtime entry.
    const captured: Array<{ name: string; def: PlopTypes.PlopGeneratorConfig }> = [];
    const plop = {
      setHelper: () => {},
      setGenerator: (n: string, d: unknown) => captured.push({ name: n, def: d as PlopTypes.PlopGeneratorConfig }),
    } as unknown as PlopTypes.NodePlopAPI;
    generator(plop);
    const corePkg = captured.find((c) => c.name === "core-package")!.def;
    const actions = (corePkg.actions as (a: { name: string }) => PlopTypes.ActionType[])(
      { name: "realtime" },
    );
    // Expectations: at least one assertNotPresent guard, multiple `add` actions, and a transpilePackages action.
    expect(actions.length).toBeGreaterThan(20); // 28 files + extras
  });
});

describe("core-package events", () => {
  it("emits actions covering package files, transpilePackages, and ESLint rule splice", () => {
    const captured: Array<{ name: string; def: PlopTypes.PlopGeneratorConfig }> = [];
    const plop = {
      setHelper: () => {},
      setGenerator: (n: string, d: unknown) => captured.push({ name: n, def: d as PlopTypes.PlopGeneratorConfig }),
    } as unknown as PlopTypes.NodePlopAPI;
    generator(plop);
    const corePkg = captured.find((c) => c.name === "core-package")!.def;
    const actions = (corePkg.actions as (a: { name: string }) => PlopTypes.ActionType[])(
      { name: "events" },
    );
    // 1 guard + 15 template files + transpilePackages + ESLint splice + printNextSteps = 19
    expect(actions.length).toBeGreaterThanOrEqual(18);
  });
});

describe("core-ui-component generator", () => {
  it("is registered with tier and name prompts", () => {
    const captured: Array<{ name: string; def: PlopTypes.PlopGeneratorConfig }> = [];
    const plopMock = {
      setHelper: () => {},
      setGenerator: (name: string, def: PlopTypes.PlopGeneratorConfig) =>
        captured.push({ name, def }),
    } as unknown as PlopTypes.NodePlopAPI;
    generator(plopMock);

    const entry = captured.find((c) => c.name === "core-ui-component");
    expect(entry).toBeDefined();
    const prompts = entry!.def.prompts as Array<{ name: string; choices?: unknown[] }>;
    expect(prompts.map((p) => p.name)).toEqual(["tier", "name"]);
    expect(prompts[0]!.choices).toEqual(["atom", "molecule", "organism"]);
  });

  it("for each tier, emits 4 add actions, 1 modify, plus guards and print", () => {
    const captured: Array<{ name: string; def: PlopTypes.PlopGeneratorConfig }> = [];
    const plopMock = {
      setHelper: () => {},
      setGenerator: (name: string, def: PlopTypes.PlopGeneratorConfig) =>
        captured.push({ name, def }),
    } as unknown as PlopTypes.NodePlopAPI;
    generator(plopMock);

    const corePkg = captured.find((c) => c.name === "core-ui-component")!.def;

    for (const tier of ["atom", "molecule", "organism"] as const) {
      const actions = (corePkg.actions as (a: { tier: string; name: string }) => PlopTypes.ActionType[])(
        { tier, name: "Spinner" },
      );

      const tierPlural = `${tier}s`;

      // 4 `add` actions, one per emitted file
      const adds = actions.filter(
        (a): a is PlopTypes.AddActionConfig =>
          typeof a === "object" && "type" in a && (a as { type: string }).type === "add",
      );
      expect(adds).toHaveLength(4);
      const addPaths = adds.map((a) => a.path);
      expect(addPaths).toContain(
        `packages/core-ui/src/${tierPlural}/{{kebabCase name}}/{{kebabCase name}}.tsx`,
      );
      expect(addPaths).toContain(
        `packages/core-ui/src/${tierPlural}/{{kebabCase name}}/{{kebabCase name}}.stories.tsx`,
      );
      expect(addPaths).toContain(
        `packages/core-ui/src/${tierPlural}/{{kebabCase name}}/{{kebabCase name}}.test.tsx`,
      );
      expect(addPaths).toContain(
        `packages/core-ui/src/${tierPlural}/{{kebabCase name}}/index.ts`,
      );

      // 1 `modify` action targeting the tier barrel
      const modifies = actions.filter(
        (a): a is PlopTypes.ModifyActionConfig =>
          typeof a === "object" && "type" in a && (a as { type: string }).type === "modify",
      );
      expect(modifies).toHaveLength(1);
      expect(modifies[0]!.path).toBe(`packages/core-ui/src/${tierPlural}/index.ts`);
      expect(String(modifies[0]!.pattern)).toContain(`<gen:${tierPlural}>`);

      // 3 function actions (2 guards + 1 print)
      const fns = actions.filter((a) => typeof a === "function");
      expect(fns).toHaveLength(3);
    }
  });

  it("PascalCase validator rejects bad names", () => {
    const captured: Array<{ name: string; def: PlopTypes.PlopGeneratorConfig }> = [];
    const plopMock = {
      setHelper: () => {},
      setGenerator: (name: string, def: PlopTypes.PlopGeneratorConfig) =>
        captured.push({ name, def }),
    } as unknown as PlopTypes.NodePlopAPI;
    generator(plopMock);

    const corePkg = captured.find((c) => c.name === "core-ui-component")!.def;
    const nameValidate = (corePkg.prompts as Array<{ name: string; validate?: (i: string) => string | true }>)
      .find((p) => p.name === "name")!.validate!;

    expect(nameValidate("")).toBe("Required");
    expect(nameValidate("spinner")).toContain("PascalCase");
    expect(nameValidate("123Foo")).toContain("PascalCase");
    expect(nameValidate("Foo-Bar")).toContain("PascalCase");
    expect(nameValidate("Spinner")).toBe(true);
    expect(nameValidate("IconButton")).toBe(true);
  });
});
