import { describe, expect, it, vi } from "vitest";
import { beginAllocationMultiDragSession } from "./timelineAllocationMultiDragSession.js";

type TestDragMode = "move" | "resize-end";

type TestMultiDragState = {
  selectedAllocationIds: string[];
  multiDragDaysDelta: number;
  isMultiDragging: boolean;
  multiDragMode: TestDragMode;
};

describe("timelineAllocationMultiDragSession", () => {
  it("starts the session, updates on day-bucket changes, and completes with selected ids", () => {
    const previousCleanup = vi.fn();
    const setState = vi.fn();
    const onComplete = vi.fn();
    const cleanupRef = { current: previousCleanup as (() => void) | null };
    const initialState: TestMultiDragState = {
      selectedAllocationIds: ["a", "b"],
      multiDragDaysDelta: 0,
      isMultiDragging: false,
      multiDragMode: "move",
    };
    const stateRef = {
      current: initialState,
    };
    const handlers: {
      move?: (event: MouseEvent) => void;
      up?: (event: MouseEvent) => void;
    } = {};

    beginAllocationMultiDragSession({
      startMouseX: 100,
      dragMode: "resize-end" as const,
      documentTarget: {} as Document,
      cleanupRef,
      stateRef,
      setState,
      startState: (state, dragMode): TestMultiDragState => ({
        ...state,
        isMultiDragging: true,
        multiDragMode: dragMode,
        multiDragDaysDelta: 0,
      }),
      updateState: (state, daysDelta) => (daysDelta === state.multiDragDaysDelta ? null : { ...state, multiDragDaysDelta: daysDelta }),
      finalizeState: (state): TestMultiDragState => ({ ...state, isMultiDragging: false, multiDragDaysDelta: 0 }),
      toDaysDelta: (deltaX) => Math.round(deltaX / 40),
      onComplete,
      attachDrag: (_documentTarget, onMove, onUp) => {
        handlers.move = onMove;
        handlers.up = onUp;
        return vi.fn();
      },
    });

    expect(previousCleanup).toHaveBeenCalledOnce();
    expect(setState).toHaveBeenNthCalledWith(
      1,
      expect.objectContaining({ isMultiDragging: true, multiDragMode: "resize-end", multiDragDaysDelta: 0 }),
    );

    handlers.move?.({ clientX: 180 } as MouseEvent);
    expect(setState).toHaveBeenNthCalledWith(
      2,
      expect.objectContaining({ isMultiDragging: true, multiDragDaysDelta: 2 }),
    );

    const attachedCleanup = cleanupRef.current;
    handlers.up?.({ clientX: 220 } as MouseEvent);

    expect(attachedCleanup).toHaveBeenCalledOnce();
    expect(cleanupRef.current).toBeNull();
    expect(setState).toHaveBeenNthCalledWith(
      3,
      expect.objectContaining({ isMultiDragging: false, multiDragDaysDelta: 0 }),
    );
    expect(onComplete).toHaveBeenCalledWith(
      3,
      expect.objectContaining({ selectedAllocationIds: ["a", "b"], isMultiDragging: false, multiDragDaysDelta: 0 }),
    );
  });

  it("skips redundant updates and suppresses completion callbacks for zero-delta releases", () => {
    const setState = vi.fn();
    const onComplete = vi.fn();
    let upHandler: ((event: MouseEvent) => void) | undefined;
    let moveHandler: ((event: MouseEvent) => void) | undefined;
    const initialState: TestMultiDragState = {
      selectedAllocationIds: ["a", "b"],
      multiDragDaysDelta: 0,
      isMultiDragging: false,
      multiDragMode: "move",
    };

    beginAllocationMultiDragSession({
      startMouseX: 100,
      dragMode: "move" as const,
      documentTarget: {} as Document,
      cleanupRef: { current: null },
      stateRef: {
        current: initialState,
      },
      setState,
      startState: (state, dragMode): TestMultiDragState => ({
        ...state,
        isMultiDragging: true,
        multiDragMode: dragMode,
        multiDragDaysDelta: 0,
      }),
      updateState: (state, daysDelta) => (daysDelta === state.multiDragDaysDelta ? null : { ...state, multiDragDaysDelta: daysDelta }),
      finalizeState: (state): TestMultiDragState => ({ ...state, isMultiDragging: false, multiDragDaysDelta: 0 }),
      toDaysDelta: (deltaX) => Math.round(deltaX / 40),
      onComplete,
      attachDrag: (_documentTarget, onMove, onUp) => {
        moveHandler = onMove;
        upHandler = onUp;
        return vi.fn();
      },
    });

    moveHandler?.({ clientX: 110 } as MouseEvent);
    upHandler?.({ clientX: 110 } as MouseEvent);

    expect(setState).toHaveBeenCalledTimes(2);
    expect(onComplete).not.toHaveBeenCalled();
  });
});
