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

import {
  DEFAULT_TOOLCRAFT_ORIENTATION_POSE,
  projectToolcraftOrientationAxes,
  readToolcraftOrientationPose,
} from "./orientation-gizmo-math";
import {
  mockModelSurfaceBounds,
  mockOrientationGizmoBounds,
  renderOrientationGizmoRuntime,
} from "./orientation-gizmo-test-harness";

function clickPositiveX(gizmo: HTMLElement, pointerId: number): void {
  fireEvent.pointerDown(gizmo, {
    button: 0,
    clientX: 59.5,
    clientY: 35,
    pointerId,
  });
  fireEvent.pointerUp(gizmo, { pointerId });
}

describe("Toolcraft orientation gizmo snap integration", () => {
  it("snaps an axis and lets the section reset restore the hidden pose target", async () => {
    const store = renderOrientationGizmoRuntime();
    const gizmo = await screen.findByTestId("toolcraft-orientation-gizmo");
    mockOrientationGizmoBounds(gizmo);
    vi.useFakeTimers();

    clickPositiveX(gizmo, 5);
    act(() => {
      vi.advanceTimersByTime(220);
    });

    const snapped = readToolcraftOrientationPose(
      store.getState().values["view.orbit"],
    );
    expect(snapped.position[0]).toBeCloseTo(5, 8);
    expect(snapped.position[1]).toBeCloseTo(0, 8);
    expect(snapped.position[2]).toBeCloseTo(0, 8);

    fireEvent.click(screen.getByRole("button", { name: "Reset Model section" }));
    expect(store.getState().values["view.orbit"]).toEqual(
      DEFAULT_TOOLCRAFT_ORIENTATION_POSE,
    );
  });

  it("records one completed axis snap as one undoable history step", async () => {
    const store = renderOrientationGizmoRuntime();
    const gizmo = await screen.findByTestId("toolcraft-orientation-gizmo");
    mockOrientationGizmoBounds(gizmo);
    vi.useFakeTimers();

    clickPositiveX(gizmo, 51);
    act(() => {
      vi.advanceTimersByTime(120);
    });

    expect(store.getState().history.undo).toHaveLength(1);
    act(() => {
      store.dispatch({ type: "history.undo" });
    });
    expect(store.getState().values["view.orbit"]).toEqual(
      DEFAULT_TOOLCRAFT_ORIENTATION_POSE,
    );
  });

  it("cancels an older snap when another endpoint is clicked", async () => {
    const store = renderOrientationGizmoRuntime();
    const gizmo = await screen.findByTestId("toolcraft-orientation-gizmo");
    mockOrientationGizmoBounds(gizmo);
    vi.useFakeTimers();

    clickPositiveX(gizmo, 52);
    act(() => {
      vi.advanceTimersByTime(32);
    });

    const currentPose = readToolcraftOrientationPose(
      store.getState().values["view.orbit"],
    );
    const negativeX = projectToolcraftOrientationAxes(currentPose).find(
      (projection) => projection.axis === "-x",
    );
    expect(negativeX).toBeDefined();
    fireEvent.pointerDown(gizmo, {
      button: 0,
      clientX: negativeX?.x,
      clientY: negativeX?.y,
      pointerId: 53,
    });
    expect(gizmo.getAttribute("data-hovered-axis")).toBe("-x");
    fireEvent.pointerUp(gizmo, { pointerId: 53 });
    act(() => {
      vi.advanceTimersByTime(220);
    });

    const finalPose = readToolcraftOrientationPose(
      store.getState().values["view.orbit"],
    );
    expect(finalPose.position[0]).toBeCloseTo(-5, 8);
    expect(finalPose.position[1]).toBeCloseTo(0, 8);
    expect(finalPose.position[2]).toBeCloseTo(0, 8);
    const historyLength = store.getState().history.undo.length;
    act(() => {
      vi.advanceTimersByTime(220);
    });
    expect(store.getState().values["view.orbit"]).toEqual(finalPose);
    expect(store.getState().history.undo).toHaveLength(historyLength);
  });

  it("does not let an in-flight axis snap overwrite a later section reset", async () => {
    const store = renderOrientationGizmoRuntime();
    const gizmo = await screen.findByTestId("toolcraft-orientation-gizmo");
    mockOrientationGizmoBounds(gizmo);
    vi.useFakeTimers();

    clickPositiveX(gizmo, 6);
    act(() => {
      vi.advanceTimersByTime(48);
    });
    expect(store.getState().values["view.orbit"]).not.toEqual(
      DEFAULT_TOOLCRAFT_ORIENTATION_POSE,
    );

    fireEvent.click(screen.getByRole("button", { name: "Reset Model section" }));
    expect(store.getState().values["view.orbit"]).toEqual(
      DEFAULT_TOOLCRAFT_ORIENTATION_POSE,
    );
    act(() => {
      vi.advanceTimersByTime(220);
    });

    expect(store.getState().values["view.orbit"]).toEqual(
      DEFAULT_TOOLCRAFT_ORIENTATION_POSE,
    );
  });

  it("does not let an old snap resume after direct model orbit takes ownership", async () => {
    const store = renderOrientationGizmoRuntime();
    const gizmo = await screen.findByTestId("toolcraft-orientation-gizmo");
    const surface = screen.getByTestId("model-surface");
    mockOrientationGizmoBounds(gizmo);
    mockModelSurfaceBounds(surface);
    vi.useFakeTimers();

    clickPositiveX(gizmo, 7);
    act(() => {
      vi.advanceTimersByTime(48);
    });

    fireEvent.pointerDown(surface, {
      button: 0,
      clientX: 100,
      clientY: 100,
      pointerId: 8,
    });
    fireEvent.pointerMove(surface, {
      clientX: 128,
      clientY: 118,
      pointerId: 8,
    });
    act(() => {
      vi.advanceTimersByTime(20);
    });
    fireEvent.pointerUp(surface, { pointerId: 8 });

    const directOrbitPose = store.getState().values["view.orbit"];
    const historyLength = store.getState().history.undo.length;
    act(() => {
      vi.advanceTimersByTime(220);
    });

    expect(store.getState().values["view.orbit"]).toEqual(directOrbitPose);
    expect(store.getState().history.undo).toHaveLength(historyLength);
  });
});
