import { describe, expect, it } from "vitest";
import {
  defineToolcraft,
  fireEvent,
  renderControlsPanelWithSchema,
  screen,
  waitFor,
} from "../testing/controls-panel-test-utils";

function renderFontPickerControl() {
  const schema = defineToolcraft({
    canvas: { enabled: true },
    panels: {
      controls: {
        sections: [
          {
            controls: {
              font: {
                defaultValue: {
                  fontId: "inter",
                  fontSize: 16,
                  fontWeight: "400",
                  letterSpacing: "normal",
                  lineHeight: "normal",
                  opacity: 100,
                  textCase: "uppercase",
                },
                label: "Font",
                target: "typography.font",
                type: "fontPicker",
              },
            },
            layout: "standalone",
            title: "Typography",
          },
        ],
        title: "Generation Controls",
      },
    },
  });

  return renderControlsPanelWithSchema(schema);
}

function openFontPicker() {
  fireEvent.click(screen.getByRole("button", { name: "Select Font" }));
}

function getFontValue() {
  return JSON.parse(screen.getByTestId("font-value").textContent ?? "{}");
}

function nextAnimationFrame() {
  return new Promise<void>((resolve) => {
    window.requestAnimationFrame(() => resolve());
  });
}

describe("ControlsPanel font picker controls", () => {
  it("renders font picker as one compound popup control shell", () => {
    renderFontPickerControl();

    expect(screen.getByText("Case")).toBeTruthy();
    expect(screen.getByRole("combobox", { name: "Text case" }).textContent).toContain(
      "Uppercase",
    );
    const fontControlItem = screen
      .getByText("Weight")
      .closest("[data-control-item-compound-context]");
    expect(fontControlItem).toBeNull();

    openFontPicker();

    const triggerValue = document.querySelector<HTMLElement>(
      '[data-slot="font-picker-trigger-value"]',
    );
    const trigger = triggerValue?.closest<HTMLElement>('[data-slot="select-trigger"]');
    expect(trigger).toBeTruthy();
    expect(trigger?.getAttribute("data-variant")).toBe("default");
    expect(trigger?.getAttribute("data-size")).toBe("default");
    expect(trigger?.className).toContain("h-7");
    expect(trigger?.className).not.toContain("h-9");
    expect(triggerValue?.style.fontFamily).toContain("Inter");
    expect(triggerValue?.style.fontWeight).toBe("400");
    expect(screen.getByText("Weight")).toBeTruthy();
    const familyWeightRow = document.querySelector<HTMLElement>(
      '[data-slot="font-picker-family-weight-row"]',
    );
    expect(familyWeightRow?.className).toContain("grid-cols-2");
    expect(familyWeightRow?.className).not.toContain("max-content");
    expect(screen.getByText("Size")).toBeTruthy();
    expect((screen.getByLabelText("Font size") as HTMLInputElement).value).toBe("16");
    expect(screen.getByText("Color")).toBeTruthy();
    expect(screen.getByLabelText("Color opacity")).toBeTruthy();
    expect(screen.getByPlaceholderText("Find font")).toBeTruthy();
    expect(screen.getByText("Sans").closest("button")?.getAttribute("data-state")).toBe(
      "active",
    );
    expect(screen.getByLabelText("Letter spacing")).toBeTruthy();
    expect(screen.getByLabelText("Line height")).toBeTruthy();
    const footerLabels = document.querySelectorAll<HTMLElement>(
      '[data-slot="font-picker-footer-label"]',
    );
    expect(footerLabels).toHaveLength(0);
    const footerIcons = document.querySelectorAll<HTMLElement>(
      '[data-slot="font-picker-footer-icon"]',
    );
    expect(footerIcons).toHaveLength(2);
    expect(footerIcons[0]?.getAttribute("class")).toContain("size-4");
    expect(footerIcons[1]?.getAttribute("class")).toContain("size-4");
    const footerSliders = document.querySelectorAll<HTMLElement>(
      '[data-slot="font-picker-footer-slider"] [data-slot="slider"]',
    );
    expect(footerSliders).toHaveLength(2);
    expect(footerSliders[0]?.getAttribute("data-variant")).toBe("discrete");
    expect(footerSliders[1]?.getAttribute("data-variant")).toBe("discrete");
    expect(footerSliders[0]?.className).toContain("[&_[data-slot=slider-range]]:transition-none");
    expect(footerSliders[0]?.className).toContain("[&_[data-slot=slider-thumb]]:transition-none");
    expect(footerSliders[1]?.className).toContain("[&_[data-slot=slider-range]]:transition-none");
    expect(footerSliders[1]?.className).toContain("[&_[data-slot=slider-thumb]]:transition-none");
    expect(
      footerSliders[0]?.querySelectorAll('[data-slot="slider-marker"]'),
    ).toHaveLength(4);
    expect(
      footerSliders[1]?.querySelectorAll('[data-slot="slider-marker"]'),
    ).toHaveLength(4);
    const lineHeightMarkerOffsets = Array.from(
      footerSliders[1]?.querySelectorAll<HTMLElement>('[data-slot="slider-marker"]') ??
        [],
    ).map((marker) => Number.parseFloat(marker.style.left));
    expect(lineHeightMarkerOffsets).toHaveLength(4);
    expect(lineHeightMarkerOffsets[0]).toBeCloseTo(20);
    expect(lineHeightMarkerOffsets[1]).toBeCloseTo(40);
    expect(lineHeightMarkerOffsets[2]).toBeCloseTo(60);
    expect(lineHeightMarkerOffsets[3]).toBeCloseTo(80);
  });

  it("commits font family, spacing, line-height, and size values", async () => {
    renderFontPickerControl();
    openFontPicker();

    const triggerValue = document.querySelector<HTMLElement>(
      '[data-slot="font-picker-trigger-value"]',
    );

    fireEvent.click(screen.getByText("Roboto").closest("button") as HTMLButtonElement);
    fireEvent.change(screen.getByLabelText("Letter spacing"), {
      target: { value: "4" },
    });
    fireEvent.change(screen.getByLabelText("Line height"), {
      target: { value: "5" },
    });

    await waitFor(() => {
      expect(getFontValue()).toEqual({
        color: "#FFFFFF",
        fontId: "roboto",
        fontSize: 16,
        fontWeight: "400",
        letterSpacing: "wider",
        lineHeight: "loose",
        opacity: 100,
        textCase: "uppercase",
      });
    });
    expect(triggerValue?.style.fontFamily).toContain("Roboto");

    fireEvent.change(screen.getByLabelText("Line height"), {
      target: { value: "4.4" },
    });

    await waitFor(() => {
      expect(getFontValue()).toEqual({
        color: "#FFFFFF",
        fontId: "roboto",
        fontSize: 16,
        fontWeight: "400",
        letterSpacing: "wider",
        lineHeight: "relaxed",
        opacity: 100,
        textCase: "uppercase",
      });
    });

    const fontSizeInput = screen.getByLabelText("Font size") as HTMLInputElement;

    fireEvent.change(fontSizeInput, {
      target: { value: "24" },
    });
    expect(getFontValue()).toEqual({
      color: "#FFFFFF",
      fontId: "roboto",
      fontSize: 16,
      fontWeight: "400",
      letterSpacing: "wider",
      lineHeight: "relaxed",
      opacity: 100,
      textCase: "uppercase",
    });

    fireEvent.blur(fontSizeInput);

    await waitFor(() => {
      expect(getFontValue()).toEqual({
        color: "#FFFFFF",
        fontId: "roboto",
        fontSize: 24,
        fontWeight: "400",
        letterSpacing: "wider",
        lineHeight: "relaxed",
        opacity: 100,
        textCase: "uppercase",
      });
    });

    fireEvent.change(fontSizeInput, {
      target: { value: "768" },
    });
    fireEvent.blur(fontSizeInput);

    await waitFor(() => {
      expect(getFontValue()).toEqual({
        color: "#FFFFFF",
        fontId: "roboto",
        fontSize: 768,
        fontWeight: "400",
        letterSpacing: "wider",
        lineHeight: "relaxed",
        opacity: 100,
        textCase: "uppercase",
      });
    });
  });

  it("commits color edits and resets empty font size to the default", async () => {
    renderFontPickerControl();
    openFontPicker();

    const colorHexInput = screen.getByLabelText("Color hex") as HTMLInputElement;
    fireEvent.change(colorHexInput, {
      target: { value: "#C1FF00" },
    });
    fireEvent.blur(colorHexInput);
    const colorOpacityInput = screen.getByLabelText("Color opacity") as HTMLInputElement;
    fireEvent.change(colorOpacityInput, {
      target: { value: "82" },
    });
    fireEvent.blur(colorOpacityInput);

    await waitFor(() => {
      expect(getFontValue()).toEqual({
        color: "#C1FF00",
        fontId: "inter",
        fontSize: 16,
        fontWeight: "400",
        letterSpacing: "normal",
        lineHeight: "normal",
        opacity: 82,
        textCase: "uppercase",
      });
    });

    const fontSizeInput = screen.getByLabelText("Font size") as HTMLInputElement;

    fireEvent.change(fontSizeInput, {
      target: { value: "" },
    });
    expect(fontSizeInput.value).toBe("");

    fireEvent.blur(fontSizeInput);
    expect((screen.getByLabelText("Font size") as HTMLInputElement).value).toBe("16");
  });

  it("virtualizes the font list with the shared Toolcraft scrollbar", async () => {
    renderFontPickerControl();
    openFontPicker();

    const listViewport = document.querySelector<HTMLElement>(
      '[data-slot="font-picker-list-viewport"]',
    );
    expect(listViewport).toBeTruthy();
    expect(listViewport?.className).toContain("toolcraft-scrollbar");
    expect(listViewport?.className).not.toContain("no-scrollbar");
    await nextAnimationFrame();
    await nextAnimationFrame();

    Object.defineProperty(listViewport, "clientHeight", {
      configurable: true,
      value: 240,
    });
    listViewport!.scrollTop = 4800;
    fireEvent.scroll(listViewport!);
    await nextAnimationFrame();

    await waitFor(() => {
      const firstVirtualFont = document.querySelector<HTMLElement>(
        '[data-slot="font-picker-list"] button span',
      );
      expect(firstVirtualFont?.textContent).not.toBe("Inter");
    });
  });
});
