import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
import type { ReactNode } from "react";

// next-themes accesses window/localStorage on import; stub the hook so the
// component is testable in a Node environment.
const setThemeMock = vi.fn();
const useThemeReturn: { theme: string | undefined; setTheme: typeof setThemeMock } = {
  theme: "system",
  setTheme: setThemeMock,
};
vi.mock("next-themes", () => ({
  useTheme: () => useThemeReturn,
}));

// Radix DropdownMenu uses portals + client refs. Render trigger and items
// inline so we can assert on the markup deterministically.
vi.mock("@/components/ui/dropdown-menu", () => ({
  DropdownMenu: ({ children }: { children: ReactNode }) => <>{children}</>,
  DropdownMenuTrigger: ({ children }: { children: ReactNode; asChild?: boolean }) => <>{children}</>,
  DropdownMenuContent: ({ children }: { children: ReactNode }) => (
    <div data-testid="dropdown-content">{children}</div>
  ),
  DropdownMenuItem: ({
    children,
    onClick,
  }: {
    children: ReactNode;
    onClick?: () => void;
  }) => (
    <button type="button" onClick={onClick} data-testid="dropdown-item">
      {children}
    </button>
  ),
}));

import { ThemeToggle } from "./theme-toggle";

function setTheme(t: string | undefined) {
  useThemeReturn.theme = t;
}

describe("ThemeToggle — rendered markup", () => {
  beforeEach(() => {
    setThemeMock.mockReset();
    setTheme("system");
  });

  it("shows the system label + monitor icon when theme is 'system'", () => {
    setTheme("system");
    const html = renderToStaticMarkup(<ThemeToggle />);
    expect(html).toMatch(/lucide-monitor/);
    expect(html).toContain(">system<");
  });

  it("shows the light label + sun icon when theme is 'light'", () => {
    setTheme("light");
    const html = renderToStaticMarkup(<ThemeToggle />);
    expect(html).toMatch(/lucide-sun/);
    expect(html).toContain(">light<");
  });

  it("shows the dark label + moon icon when theme is 'dark'", () => {
    setTheme("dark");
    const html = renderToStaticMarkup(<ThemeToggle />);
    expect(html).toMatch(/lucide-moon/);
    expect(html).toContain(">dark<");
  });

  it("falls back to 'system' label when next-themes returns undefined (pre-mount)", () => {
    setTheme(undefined);
    const html = renderToStaticMarkup(<ThemeToggle />);
    expect(html).toContain(">system<");
  });

  it("renders three menu items (Light / Dark / System)", () => {
    setTheme("system");
    const html = renderToStaticMarkup(<ThemeToggle />);
    expect(html).toContain("Light");
    expect(html).toContain("Dark");
    expect(html).toContain("System");
    // One trigger + three menu items.
    const items = html.match(/data-testid="dropdown-item"/g) ?? [];
    expect(items).toHaveLength(3);
  });
});

describe("ThemeToggle — onClick wires through to setTheme", () => {
  beforeEach(() => {
    setThemeMock.mockReset();
    setTheme("system");
  });

  // We can't realistically click a server-rendered button. Instead reach
  // into the rendered React tree and grab the onClick handlers — that's
  // exactly what the click handler will fire in the browser, so it's a
  // faithful contract check.
  function getMenuItemHandlers(): Array<() => void> {
    // Render via React.createElement to keep the element tree.
    // Then walk it for the dropdown-item buttons.
    // (Easier than parsing the SSR markup.)
    const el = (<ThemeToggle />) as unknown as React.ReactElement;
    const handlers: Array<() => void> = [];
    function visit(node: unknown): void {
      if (!node) return;
      if (Array.isArray(node)) {
        for (const c of node) visit(c);
        return;
      }
      if (typeof node !== "object") return;
      const n = node as { props?: Record<string, unknown>; type?: unknown };
      const props = n.props ?? {};
      if (typeof props.onClick === "function" && props["data-testid"] === "dropdown-item") {
        handlers.push(props.onClick as () => void);
      }
      // type can be a function component — render it once with its props
      // so we can recurse into its output. Component functions used here
      // (DropdownMenuItem mocks etc.) are pure render-children.
      if (typeof n.type === "function") {
        const rendered = (n.type as (p: unknown) => unknown)(props);
        visit(rendered);
      }
      if ("children" in props) {
        visit(props.children);
      }
    }
    visit(el);
    return handlers;
  }

  it("first menu item sets theme to 'light'", () => {
    const [light] = getMenuItemHandlers();
    light?.();
    expect(setThemeMock).toHaveBeenCalledTimes(1);
    expect(setThemeMock).toHaveBeenCalledWith("light");
  });

  it("second menu item sets theme to 'dark'", () => {
    const [, dark] = getMenuItemHandlers();
    dark?.();
    expect(setThemeMock).toHaveBeenCalledWith("dark");
  });

  it("third menu item sets theme to 'system'", () => {
    const [, , system] = getMenuItemHandlers();
    system?.();
    expect(setThemeMock).toHaveBeenCalledWith("system");
  });
});
