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

import type { ToolcraftLayer } from "../../state/types";
import {
  cleanupLayersPanelTestEnv,
  installLayersPanelMatchMedia,
  mockLayerRowRect,
  renderLayersPanel,
  seededLayers,
} from "./layers-panel-test-utils";

beforeAll(() => {
  installLayersPanelMatchMedia();
});

afterEach(() => {
  cleanupLayersPanelTestEnv();
});

describe("LayersPanel drag and drop", () => {
  it("drags layers into groups and keeps the moved layer under the group", () => {
    const { container } = renderLayersPanel();
    const groupRow = container.querySelector<HTMLElement>('[data-layer-id="group-1"]');
    const layerRow = container.querySelector<HTMLElement>('[data-layer-id="layer-2"]');

    expect(groupRow).toBeTruthy();
    expect(layerRow).toBeTruthy();

    mockLayerRowRect(groupRow as HTMLElement);
    Object.defineProperty(document, "elementFromPoint", {
      configurable: true,
      value: vi.fn(() => groupRow),
    });

    fireEvent.pointerDown(layerRow as HTMLElement, {
      button: 0,
      clientX: 0,
      clientY: 0,
      pointerId: 1,
    });
    fireEvent.pointerMove(layerRow as HTMLElement, {
      clientX: 12,
      clientY: 16,
      pointerId: 1,
    });
    fireEvent.pointerUp(layerRow as HTMLElement, { pointerId: 1 });

    expect(screen.getByTestId("layer-tree").textContent).toBe(
      "group-1:root|layer-2:group-1|layer-1:group-1",
    );
  });

  it("opens closed groups after dropping a layer into them", () => {
    const collapsedLayers = [
      { ...seededLayers[0]!, collapsed: true },
      seededLayers[1]!,
      seededLayers[2]!,
    ];
    const { container } = renderLayersPanel({}, collapsedLayers);
    const groupRow = container.querySelector<HTMLElement>('[data-layer-id="group-1"]');
    const layerRow = container.querySelector<HTMLElement>('[data-layer-id="layer-2"]');

    expect(groupRow).toBeTruthy();
    expect(layerRow).toBeTruthy();
    expect(screen.queryByText("Layer 1")).toBeNull();

    mockLayerRowRect(groupRow as HTMLElement);
    Object.defineProperty(document, "elementFromPoint", {
      configurable: true,
      value: vi.fn(() => groupRow),
    });

    fireEvent.pointerDown(layerRow as HTMLElement, {
      button: 0,
      clientX: 0,
      clientY: 0,
      pointerId: 1,
    });
    fireEvent.pointerMove(layerRow as HTMLElement, {
      clientX: 12,
      clientY: 16,
      pointerId: 1,
    });
    fireEvent.pointerUp(layerRow as HTMLElement, { pointerId: 1 });

    expect(screen.getByTestId("group-collapsed").textContent).toBe("false");
    expect(screen.getByTestId("layer-tree").textContent).toBe(
      "group-1:root|layer-2:group-1|layer-1:group-1",
    );
    expect(screen.getByText("Layer 1")).toBeTruthy();
  });

  it("highlights group targets and suppresses ordinary row hover during drag", () => {
    const layers: ToolcraftLayer[] = [
      ...seededLayers,
      {
        displayName: "Layer 3",
        id: "layer-3",
        kind: "layer",
        name: "layer-3",
        visible: true,
      },
    ];
    const { container } = renderLayersPanel({}, layers);
    const groupRow = container.querySelector<HTMLElement>('[data-layer-id="group-1"]');
    const dragRow = container.querySelector<HTMLElement>('[data-layer-id="layer-2"]');
    const idleRow = container.querySelector<HTMLElement>('[data-layer-id="layer-3"]');

    expect(groupRow).toBeTruthy();
    expect(dragRow).toBeTruthy();
    expect(idleRow).toBeTruthy();

    mockLayerRowRect(groupRow as HTMLElement);
    Object.defineProperty(document, "elementFromPoint", {
      configurable: true,
      value: vi.fn(() => groupRow),
    });

    fireEvent.pointerDown(dragRow as HTMLElement, {
      button: 0,
      clientX: 0,
      clientY: 0,
      pointerId: 1,
    });
    fireEvent.pointerMove(dragRow as HTMLElement, {
      clientX: 12,
      clientY: 16,
      pointerId: 1,
    });

    const groupSurface = groupRow?.querySelector<HTMLElement>('[data-layer-row-surface]');
    const idleSurface = idleRow?.querySelector<HTMLElement>('[data-layer-row-surface]');
    const idleActions = idleRow?.querySelector<HTMLElement>("[data-layer-actions]");

    expect(groupSurface?.className).toContain("border-[color:var(--accent)]");
    expect(groupRow?.dataset.dropTarget).toBe("true");
    expect(groupRow?.dataset.reorderDragging).toBe("true");
    expect(idleRow?.dataset.reorderDragging).toBe("true");
    expect(groupSurface?.className).toContain(
      "bg-[color:color-mix(in_oklab,var(--accent)_15%,transparent)]",
    );
    expect(idleSurface?.className).not.toContain("hover:bg");
    expect(idleActions?.className).not.toContain("group-hover/layer");
  });

  it("uses pointer depth to drag layers out of nested groups", () => {
    const nestedLayers = [
      seededLayers[0],
      seededLayers[1],
      { ...seededLayers[2], parentGroupId: "group-1" },
      {
        displayName: "Layer 3",
        id: "layer-3",
        kind: "layer" as const,
        name: "layer-3",
        visible: true,
      },
    ];
    const { container } = renderLayersPanel({}, nestedLayers);
    const sourceRow = container.querySelector<HTMLElement>('[data-layer-id="layer-1"]');
    const targetRow = container.querySelector<HTMLElement>('[data-layer-id="layer-2"]');

    expect(sourceRow).toBeTruthy();
    expect(targetRow).toBeTruthy();

    mockLayerRowRect(targetRow as HTMLElement, 32);
    Object.defineProperty(document, "elementFromPoint", {
      configurable: true,
      value: vi.fn(() => targetRow),
    });

    fireEvent.pointerDown(sourceRow as HTMLElement, {
      button: 0,
      clientX: 40,
      clientY: 16,
      pointerId: 1,
    });
    fireEvent.pointerMove(sourceRow as HTMLElement, {
      clientX: 0,
      clientY: 60,
      pointerId: 1,
    });
    fireEvent.pointerUp(sourceRow as HTMLElement, { pointerId: 1 });

    expect(screen.getByTestId("layer-tree").textContent).toBe(
      "group-1:root|layer-2:group-1|layer-1:root|layer-3:root",
    );
  });
});
