import { describe, expect, it, vi } from "vitest";
import { beginCanvasMultiSelectSession } from "./timelineMultiSelectSession.js";

type TestState = {
  isSelecting: boolean;
  cursor: string;
};

describe("timelineMultiSelectSession", () => {
  it("starts a session, forwards move updates, and finalizes on mouseup", () => {
    const previousCleanup = vi.fn();
    const setState = vi.fn();
    const createInitialState = vi.fn((clientX: number, clientY: number) => ({
      isSelecting: true,
      cursor: `${clientX}:${clientY}`,
    }));
    const updateState = vi.fn((_: TestState, clientX: number, clientY: number) => ({
      isSelecting: true,
      cursor: `${clientX}:${clientY}`,
    }));
    const completeState = vi.fn((_: TestState, clientX: number, clientY: number, initialState: TestState) => ({
      nextState: {
        ...initialState,
        isSelecting: false,
        cursor: `done:${clientX}:${clientY}`,
      },
    }));

    const cleanupRef = { current: previousCleanup as (() => void) | null };
    const stateRef = { current: { isSelecting: false, cursor: "initial" } };
    const initialState = { isSelecting: false, cursor: "idle" };
    const handlers: {
      move?: (event: MouseEvent) => void;
      up?: (event: MouseEvent) => void;
    } = {};

    const attachDrag = vi.fn((_: Document, onMove: (event: MouseEvent) => void, onUp: (event: MouseEvent) => void) => {
      handlers.move = onMove;
      handlers.up = onUp;
      return vi.fn();
    });

    beginCanvasMultiSelectSession({
      clientX: 10,
      clientY: 20,
      documentTarget: {} as Document,
      cleanupRef,
      stateRef,
      setState,
      createInitialState,
      updateState,
      completeState,
      initialState,
      attachDrag,
    });

    expect(previousCleanup).toHaveBeenCalledOnce();
    expect(setState).toHaveBeenCalledWith({ isSelecting: true, cursor: "10:20" });
    expect(typeof handlers.move).toBe("function");
    expect(typeof handlers.up).toBe("function");

    handlers.move?.({ clientX: 14, clientY: 24 } as MouseEvent);
    expect(updateState).toHaveBeenCalledWith({ isSelecting: true, cursor: "10:20" }, 14, 24);
    expect(stateRef.current).toEqual({ isSelecting: true, cursor: "14:24" });

    const attachedCleanup = cleanupRef.current;
    expect(attachedCleanup).not.toBeNull();
    handlers.up?.({ clientX: 18, clientY: 28 } as MouseEvent);

    expect(completeState).toHaveBeenCalledWith({ isSelecting: true, cursor: "14:24" }, 18, 28, initialState);
    expect(setState).toHaveBeenLastCalledWith({ isSelecting: false, cursor: "done:18:28" });
    expect(stateRef.current).toEqual({ isSelecting: false, cursor: "done:18:28" });
    expect(cleanupRef.current).toBeNull();
    expect(attachedCleanup).toHaveBeenCalledOnce();
  });

  it("stops updating when selection is already inactive before move and mouseup", () => {
    const setState = vi.fn();
    const updateState = vi.fn();
    const completeState = vi.fn();
    const cleanupRef = { current: null as (() => void) | null };
    const stateRef = { current: { isSelecting: false, cursor: "idle" } };
    const handlers: {
      move?: (event: MouseEvent) => void;
      up?: (event: MouseEvent) => void;
    } = {};

    beginCanvasMultiSelectSession({
      clientX: 1,
      clientY: 2,
      documentTarget: {} as Document,
      cleanupRef,
      stateRef,
      setState,
      createInitialState: () => ({ isSelecting: true, cursor: "start" }),
      updateState,
      completeState,
      initialState: { isSelecting: false, cursor: "idle" },
      attachDrag: (_documentTarget, onMove, onUp) => {
        handlers.move = onMove;
        handlers.up = onUp;
        return vi.fn();
      },
    });

    stateRef.current = { isSelecting: false, cursor: "cancelled" };

    handlers.move?.({ clientX: 3, clientY: 4 } as MouseEvent);
    handlers.up?.({ clientX: 5, clientY: 6 } as MouseEvent);

    expect(updateState).not.toHaveBeenCalled();
    expect(completeState).not.toHaveBeenCalled();
    expect(setState).toHaveBeenCalledTimes(1);
    expect(cleanupRef.current).toBeNull();
  });
});
