import { describe, expect, it, vi } from "vitest";
import { finalizeAllocationReleaseEffects } from "./timelineAllocationReleaseEffects.js";

const BASE_ALLOC = {
  isActive: true,
  mode: "move" as const,
  scope: "allocation" as const,
  allocationId: "alloc-1",
  mutationAllocationId: "alloc-1",
  projectId: "project-1",
  projectName: "Project One",
  allocationStartDate: new Date("2025-01-01"),
  allocationEndDate: new Date("2025-01-03"),
  originalStartDate: new Date("2025-01-01"),
  originalEndDate: new Date("2025-01-03"),
  currentStartDate: new Date("2025-01-01"),
  currentEndDate: new Date("2025-01-03"),
  pointerDeltaX: 0,
  daysDelta: 0,
};

function createOptimisticStateHarness() {
  let optimistic = new Map<string, { startDate: Date; endDate: Date }>();

  return {
    get value() {
      return optimistic;
    },
    setOptimisticAllocations(
      updater: (
        prev: Map<string, { startDate: Date; endDate: Date }>,
      ) => Map<string, { startDate: Date; endDate: Date }>,
    ) {
      optimistic = updater(optimistic);
    },
  };
}

describe("timelineAllocationReleaseEffects", () => {
  it("ignores inactive releases without touching preview or optimistic state", async () => {
    const updatePosition = vi.fn();
    const preserve = vi.fn();
    const clear = vi.fn();
    const onShiftClick = vi.fn();
    const onBlockClick = vi.fn();
    const optimisticHarness = createOptimisticStateHarness();
    const pendingSnapshotRef = { current: null };
    const pendingOptimisticAllocationIdRef = { current: null as string | null };

    await finalizeAllocationReleaseEffects({
      clientX: 24,
      allocRef: { current: { ...BASE_ALLOC, isActive: false } },
      previewRef: { current: {} as never },
      updatePosition,
      clickThresholdPx: 5,
      wasShift: false,
      onShiftClick,
      onBlockClick,
      pendingSnapshotRef,
      pendingOptimisticAllocationIdRef,
      setOptimisticAllocations: optimisticHarness.setOptimisticAllocations,
      extractAllocationFragment: vi.fn(),
      updateAllocation: vi.fn(),
      clearPendingOptimisticAllocation: vi.fn(),
      previewOps: { preserve, clear },
    });

    expect(updatePosition).toHaveBeenCalledWith(24);
    expect(preserve).not.toHaveBeenCalled();
    expect(clear).not.toHaveBeenCalled();
    expect(onShiftClick).not.toHaveBeenCalled();
    expect(onBlockClick).not.toHaveBeenCalled();
    expect(pendingSnapshotRef.current).toBeNull();
    expect(pendingOptimisticAllocationIdRef.current).toBeNull();
    expect(optimisticHarness.value.size).toBe(0);
  });

  it("clears preview and dispatches click releases without starting mutations", async () => {
    const clear = vi.fn();
    const onBlockClick = vi.fn();
    const optimisticHarness = createOptimisticStateHarness();
    const previewRef = { current: {} as never };

    await finalizeAllocationReleaseEffects({
      clientX: 40,
      allocRef: { current: BASE_ALLOC },
      previewRef,
      updatePosition: vi.fn(),
      clickThresholdPx: 5,
      wasShift: false,
      onBlockClick,
      pendingSnapshotRef: { current: null },
      pendingOptimisticAllocationIdRef: { current: null },
      setOptimisticAllocations: optimisticHarness.setOptimisticAllocations,
      extractAllocationFragment: vi.fn(),
      updateAllocation: vi.fn(),
      clearPendingOptimisticAllocation: vi.fn(),
      resolveRelease: () => ({
        kind: "click",
        clickInfo: {
          allocationId: "alloc-1",
          projectId: "project-1",
          projectName: "Project One",
          startDate: new Date("2025-01-01"),
          endDate: new Date("2025-01-03"),
        },
        preservePreview: true,
      }),
      previewOps: { preserve: vi.fn(), clear },
    });

    expect(clear).toHaveBeenCalledOnce();
    expect(onBlockClick).toHaveBeenCalledWith(
      expect.objectContaining({ allocationId: "alloc-1", projectId: "project-1" }),
    );
    expect(previewRef.current).toBeNull();
    expect(optimisticHarness.value.size).toBe(0);
  });

  it("starts optimistic mutation flow and rewrites the pending snapshot after extraction", async () => {
    const preserve = vi.fn();
    const clear = vi.fn();
    const extractAllocationFragment = vi.fn().mockResolvedValue({
      extractedAllocationId: "alloc-fragment-2",
    });
    const updateAllocation = vi.fn();
    const optimisticHarness = createOptimisticStateHarness();
    const pendingSnapshotRef = {
      current: null as {
        allocationId: string;
        mutationAllocationId: string;
        projectName: string;
        before: { startDate: Date; endDate: Date };
        after: { startDate: Date; endDate: Date };
      } | null,
    };
    const pendingOptimisticAllocationIdRef = { current: null as string | null };

    await finalizeAllocationReleaseEffects({
      clientX: 44,
      allocRef: { current: BASE_ALLOC },
      previewRef: { current: {} as never },
      updatePosition: vi.fn(),
      clickThresholdPx: 5,
      wasShift: false,
      pendingSnapshotRef,
      pendingOptimisticAllocationIdRef,
      setOptimisticAllocations: optimisticHarness.setOptimisticAllocations,
      extractAllocationFragment,
      updateAllocation,
      clearPendingOptimisticAllocation: vi.fn(),
      resolveRelease: () => ({
        kind: "mutation",
        preservePreview: true,
        mutationPlan: {
          activeAllocationId: "alloc-1",
          currentStartDate: new Date("2025-01-05"),
          currentEndDate: new Date("2025-01-07"),
          baseMutationAllocationId: "alloc-1",
          requiresExtraction: true,
          pendingSnapshot: {
            allocationId: "alloc-1",
            mutationAllocationId: "alloc-1",
            projectName: "Project One",
            before: {
              startDate: new Date("2025-01-01"),
              endDate: new Date("2025-01-03"),
            },
            after: {
              startDate: new Date("2025-01-05"),
              endDate: new Date("2025-01-07"),
            },
          },
        },
      }),
      previewOps: { preserve, clear },
    });

    expect(preserve).toHaveBeenCalledOnce();
    expect(clear).toHaveBeenCalledOnce();
    expect(pendingOptimisticAllocationIdRef.current).toBe("alloc-1");
    expect(optimisticHarness.value.get("alloc-1")).toEqual({
      startDate: new Date("2025-01-05"),
      endDate: new Date("2025-01-07"),
    });
    expect(extractAllocationFragment).toHaveBeenCalledWith({
      allocationId: "alloc-1",
      startDate: new Date("2025-01-01"),
      endDate: new Date("2025-01-03"),
    });
    expect(pendingSnapshotRef.current).toEqual(
      expect.objectContaining({ mutationAllocationId: "alloc-fragment-2" }),
    );
    expect(updateAllocation).toHaveBeenCalledWith({
      allocationId: "alloc-fragment-2",
      startDate: new Date("2025-01-05"),
      endDate: new Date("2025-01-07"),
    });
  });

  it("clears optimistic state when extraction fails", async () => {
    const optimisticHarness = createOptimisticStateHarness();
    const pendingSnapshotRef = {
      current: null as {
        allocationId: string;
        mutationAllocationId: string;
        projectName: string;
        before: { startDate: Date; endDate: Date };
        after: { startDate: Date; endDate: Date };
      } | null,
    };
    const pendingOptimisticAllocationIdRef = { current: null as string | null };
    const clearPendingOptimisticAllocation = vi.fn((allocationId: string) => {
      pendingSnapshotRef.current = null;
      pendingOptimisticAllocationIdRef.current = null;
      optimisticHarness.setOptimisticAllocations((prev) => {
        const next = new Map(prev);
        next.delete(allocationId);
        return next;
      });
    });
    const updateAllocation = vi.fn();

    await finalizeAllocationReleaseEffects({
      clientX: 52,
      allocRef: { current: BASE_ALLOC },
      previewRef: { current: {} as never },
      updatePosition: vi.fn(),
      clickThresholdPx: 5,
      wasShift: false,
      pendingSnapshotRef,
      pendingOptimisticAllocationIdRef,
      setOptimisticAllocations: optimisticHarness.setOptimisticAllocations,
      extractAllocationFragment: vi.fn().mockRejectedValue(new Error("boom")),
      updateAllocation,
      clearPendingOptimisticAllocation,
      resolveRelease: () => ({
        kind: "mutation",
        preservePreview: true,
        mutationPlan: {
          activeAllocationId: "alloc-1",
          currentStartDate: new Date("2025-01-05"),
          currentEndDate: new Date("2025-01-07"),
          baseMutationAllocationId: "alloc-1",
          requiresExtraction: true,
          pendingSnapshot: {
            allocationId: "alloc-1",
            mutationAllocationId: "alloc-1",
            projectName: "Project One",
            before: {
              startDate: new Date("2025-01-01"),
              endDate: new Date("2025-01-03"),
            },
            after: {
              startDate: new Date("2025-01-05"),
              endDate: new Date("2025-01-07"),
            },
          },
        },
      }),
      previewOps: { preserve: vi.fn(), clear: vi.fn() },
    });

    expect(clearPendingOptimisticAllocation).toHaveBeenCalledWith("alloc-1");
    expect(updateAllocation).not.toHaveBeenCalled();
    expect(pendingSnapshotRef.current).toBeNull();
    expect(pendingOptimisticAllocationIdRef.current).toBeNull();
    expect(optimisticHarness.value.size).toBe(0);
  });
});
