/**
 * LD — design-system component tests. Pure UI, rendered with React Testing
 * Library; no backend, no gateways. We assert behaviour and accessibility
 * (roles, names, aria-*) rather than exact Tailwind classes, so the visual
 * design can evolve without breaking the suite.
 */

import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";

import { Button } from "./Button";
import { Input } from "./Input";
import { Field } from "./Field";
import { Tabs } from "./Tabs";

describe("Button", () => {
  it("renders its label and fires onClick", () => {
    const onClick = vi.fn();
    render(<Button onClick={onClick}>Save</Button>);
    fireEvent.click(screen.getByRole("button", { name: "Save" }));
    expect(onClick).toHaveBeenCalledOnce();
  });

  it("is disabled and shows a spinner while loading", () => {
    const onClick = vi.fn();
    render(
      <Button loading onClick={onClick}>
        Save
      </Button>,
    );
    const btn = screen.getByRole("button", { name: /Save/ }) as HTMLButtonElement;
    expect(btn.disabled).toBe(true);
    expect(screen.getByRole("status")).toBeTruthy(); // spinner
    fireEvent.click(btn);
    expect(onClick).not.toHaveBeenCalled();
  });

  it("defaults to type=button to avoid accidental form submits", () => {
    render(<Button>Go</Button>);
    expect(screen.getByRole("button", { name: "Go" }).getAttribute("type")).toBe(
      "button",
    );
  });
});

describe("Input", () => {
  it("flags aria-invalid when invalid", () => {
    render(<Input aria-label="email" invalid />);
    expect(screen.getByLabelText("email").getAttribute("aria-invalid")).toBe("true");
  });

  it("is not aria-invalid by default", () => {
    render(<Input aria-label="email" />);
    expect(screen.getByLabelText("email").getAttribute("aria-invalid")).toBeNull();
  });
});

describe("Field", () => {
  it("associates the label with the control via id", () => {
    render(
      <Field label="Project name">
        {({ id, describedBy }) => (
          <input id={id} aria-describedby={describedBy} />
        )}
      </Field>,
    );
    // getByLabelText resolves the label→control association.
    expect(screen.getByLabelText("Project name")).toBeTruthy();
  });

  it("announces an error and wires aria-describedby", () => {
    render(
      <Field label="Root" error="must be absolute">
        {({ id, describedBy }) => (
          <input id={id} aria-describedby={describedBy} />
        )}
      </Field>,
    );
    const alert = screen.getByRole("alert");
    expect(alert.textContent).toBe("must be absolute");
    const input = screen.getByLabelText("Root");
    expect(input.getAttribute("aria-describedby")).toBe(alert.id);
  });
});

describe("Tabs", () => {
  const items = [
    { id: "a", label: "alpha" },
    { id: "b", label: "beta" },
  ];

  it("marks the active tab with aria-selected and reports selection", () => {
    const onSelect = vi.fn();
    render(<Tabs items={items} value="a" onSelect={onSelect} />);

    const tabs = screen.getAllByRole("tab");
    expect(tabs).toHaveLength(2);
    const [alpha, beta] = tabs;
    expect(alpha.getAttribute("aria-selected")).toBe("true");
    expect(beta.getAttribute("aria-selected")).toBe("false");

    fireEvent.click(beta);
    expect(onSelect).toHaveBeenCalledWith("b");
  });

  it("renders a close control per tab only when onClose is given", () => {
    const onClose = vi.fn();
    const { rerender } = render(
      <Tabs items={items} value="a" onSelect={() => {}} />,
    );
    expect(screen.queryByRole("button", { name: "close alpha" })).toBeNull();

    rerender(<Tabs items={items} value="a" onSelect={() => {}} onClose={onClose} />);
    fireEvent.click(screen.getByRole("button", { name: "close beta" }));
    expect(onClose).toHaveBeenCalledWith("b");
  });
});
