import { cleanup, fireEvent, screen, waitFor } from "@testing-library/react";
import type * as React from "react";
import { afterEach, describe, expect, it, vi } from "vitest";

import type { ToolcraftExternalStore } from "../../state/toolcraft-external-store";
import { useToolcraftStore } from "../app-shell/toolcraft-store-context";
import {
  CanvasStateProbe,
  mockCanvasRect,
  renderCanvasShell,
} from "./canvas-shell-test-utils";

afterEach(() => {
  cleanup();
  vi.restoreAllMocks();
});

function CaptureStore({
  capture,
}: {
  capture: (store: ToolcraftExternalStore) => void;
}): null {
  capture(useToolcraftStore());
  return null;
}

function createGestureEvent(
  type: "gesturechange" | "gestureend" | "gesturestart",
  properties: { clientX?: number; clientY?: number; scale?: number } = {},
): Event {
  const event = new Event(type, { bubbles: true, cancelable: true });
  Object.defineProperties(
    event,
    Object.fromEntries(
      Object.entries(properties).map(([key, value]) => [
        key,
        { configurable: true, value },
      ]),
    ),
  );
  return event;
}

describe("CanvasShell pinch interactions", () => {
  it("keeps a native browser gesture pinch inside the canvas viewport", async () => {
    let store: ToolcraftExternalStore | undefined;
    renderCanvasShell({
      afterCanvas: (
        <>
          <CaptureStore capture={(value) => { store = value; }} />
          <CanvasStateProbe />
        </>
      ),
      frameProps: {
        "data-slot": "toolcraft-runtime-app",
      } as React.HTMLAttributes<HTMLDivElement>,
    });

    const canvas = screen.getByRole("application", { name: "Canvas viewport" });
    const world = canvas.querySelector("[data-toolcraft-canvas-world]") as HTMLElement;
    mockCanvasRect(canvas);
    const gestureStart = createGestureEvent("gesturestart", {
      clientX: 600,
      clientY: 300,
      scale: 1,
    });
    const gestureChange = createGestureEvent("gesturechange", {
      clientX: 600,
      clientY: 300,
      scale: 1.5,
    });

    expect(canvas.dispatchEvent(gestureStart)).toBe(false);
    expect(gestureStart.defaultPrevented).toBe(true);
    expect(canvas.dispatchEvent(gestureChange)).toBe(false);
    expect(gestureChange.defaultPrevented).toBe(true);
    expect(store?.getState().canvas).toMatchObject({
      offset: { x: -100, y: 0 },
      zoom: 150,
    });
    expect(store?.getCommittedState().canvas.zoom).toBe(100);
    await waitFor(() => {
      expect(world.style.transform).toContain("translate(-100px");
      expect(world.style.transform).toContain("scale(1.5)");
    });

    const gestureEnd = createGestureEvent("gestureend");
    expect(canvas.dispatchEvent(gestureEnd)).toBe(false);
    expect(gestureEnd.defaultPrevented).toBe(true);
    expect(store?.getCommittedState().canvas).toMatchObject({
      offset: { x: -100, y: 0 },
      zoom: 150,
    });
  });

  it("zooms and pans the canvas from a two-touch pinch gesture", () => {
    let store: ToolcraftExternalStore | undefined;
    renderCanvasShell({
      afterCanvas: (
        <>
          <CaptureStore capture={(value) => { store = value; }} />
          <CanvasStateProbe />
        </>
      ),
      children: (
        <div
          data-testid="touch-owned-product"
          onPointerDown={(event) => event.stopPropagation()}
        />
      ),
    });

    const canvas = screen.getByRole("application", { name: "Canvas viewport" });
    const touchSurface = screen.getByTestId("touch-owned-product");
    mockCanvasRect(canvas);
    fireEvent.pointerDown(touchSurface, {
      button: 0,
      clientX: 300,
      clientY: 300,
      pointerId: 1,
      pointerType: "touch",
    });
    fireEvent.pointerDown(touchSurface, {
      button: 0,
      clientX: 500,
      clientY: 300,
      pointerId: 2,
      pointerType: "touch",
    });
    fireEvent.pointerMove(touchSurface, {
      clientX: 700,
      clientY: 300,
      pointerId: 2,
      pointerType: "touch",
    });

    expect(store?.getState().canvas).toMatchObject({
      offset: { x: 100, y: 0 },
      zoom: 200,
    });
    expect(store?.getCommittedState().canvas).toMatchObject({
      offset: { x: 0, y: 0 },
      zoom: 100,
    });
    fireEvent.pointerUp(touchSurface, { pointerId: 2, pointerType: "touch" });
    fireEvent.pointerUp(touchSurface, { pointerId: 1, pointerType: "touch" });

    expect(screen.getByTestId("canvas-offset").textContent).toBe("100,0");
    expect(screen.getByTestId("canvas-zoom").textContent).toBe("200");
    expect(store?.getCommittedState().canvas).toMatchObject({
      offset: { x: 100, y: 0 },
      zoom: 200,
    });
  });
});
