import { type AllocationMovedSnapshotLike } from "./timelineAllocationFinalize.js";
import { clearLivePreview, preserveLivePreview, type LivePreviewSession } from "./timelineLivePreview.js";
import { resolveAllocationRelease, type AllocationReleaseOutcome } from "./timelineAllocationRelease.js";

type MutableCurrent<T> = { current: T };
type AllocationDragReleaseLike = Parameters<typeof resolveAllocationRelease>[0];
type AllocationBlockClickInfo = Extract<AllocationReleaseOutcome, { kind: "click" }>["clickInfo"];
type OptimisticAllocationOverride = { startDate: Date; endDate: Date };
type SetOptimisticAllocations = (
  updater: (prev: Map<string, OptimisticAllocationOverride>) => Map<string, OptimisticAllocationOverride>,
) => void;
type MutationInput = { allocationId: string; startDate: Date; endDate: Date };
type PreviewOps = {
  preserve: (preview: LivePreviewSession | null) => void;
  clear: (preview: LivePreviewSession | null) => void;
};

type FinalizeAllocationReleaseEffectsParams = {
  clientX: number;
  allocRef: MutableCurrent<AllocationDragReleaseLike>;
  previewRef: MutableCurrent<LivePreviewSession | null>;
  updatePosition: (clientX: number) => void;
  clickThresholdPx: number;
  wasShift: boolean;
  onShiftClick?: ((allocationId: string) => void) | undefined;
  onBlockClick?: ((clickInfo: AllocationBlockClickInfo) => void) | undefined;
  pendingSnapshotRef: MutableCurrent<AllocationMovedSnapshotLike | null>;
  pendingOptimisticAllocationIdRef: MutableCurrent<string | null>;
  setOptimisticAllocations: SetOptimisticAllocations;
  extractAllocationFragment: (input: MutationInput) => Promise<{ extractedAllocationId: string }>;
  updateAllocation: (input: MutationInput) => void;
  clearPendingOptimisticAllocation: (allocationId: string) => void;
  resolveRelease?: (
    alloc: AllocationDragReleaseLike,
    options: { clickThresholdPx: number; wasShift: boolean },
  ) => AllocationReleaseOutcome;
  previewOps?: PreviewOps;
};

export async function finalizeAllocationReleaseEffects({
  clientX,
  allocRef,
  previewRef,
  updatePosition,
  clickThresholdPx,
  wasShift,
  onShiftClick,
  onBlockClick,
  pendingSnapshotRef,
  pendingOptimisticAllocationIdRef,
  setOptimisticAllocations,
  extractAllocationFragment,
  updateAllocation,
  clearPendingOptimisticAllocation,
  resolveRelease = resolveAllocationRelease,
  previewOps = { preserve: preserveLivePreview, clear: clearLivePreview },
}: FinalizeAllocationReleaseEffectsParams): Promise<void> {
  updatePosition(clientX);
  const alloc = allocRef.current;
  const release = resolveRelease(alloc, { clickThresholdPx, wasShift });
  if (release.kind === "ignore") return;
  if (release.preservePreview) previewOps.preserve(previewRef.current);
  previewOps.clear(previewRef.current);
  previewRef.current = null;
  if (release.kind === "shift-click") return void onShiftClick?.(release.allocationId);
  if (release.kind === "click") return void onBlockClick?.(release.clickInfo);
  if (release.kind !== "mutation") return;

  const {
    activeAllocationId,
    currentStartDate,
    currentEndDate,
    baseMutationAllocationId,
    requiresExtraction,
    pendingSnapshot,
  } = release.mutationPlan;

  pendingSnapshotRef.current = pendingSnapshot;
  pendingOptimisticAllocationIdRef.current = activeAllocationId;
  setOptimisticAllocations((prev) => {
    const next = new Map(prev);
    next.set(activeAllocationId, { startDate: currentStartDate, endDate: currentEndDate });
    return next;
  });

  try {
    let mutationAllocationId = baseMutationAllocationId;
    if (requiresExtraction) {
      if (!alloc.originalStartDate || !alloc.originalEndDate) throw new Error("missing original allocation dates");
      const extracted = await extractAllocationFragment({
        allocationId: mutationAllocationId,
        startDate: alloc.originalStartDate,
        endDate: alloc.originalEndDate,
      });
      mutationAllocationId = extracted.extractedAllocationId;
    }
    pendingSnapshotRef.current = pendingSnapshotRef.current
      ? { ...pendingSnapshotRef.current, mutationAllocationId }
      : null;
    updateAllocation({ allocationId: mutationAllocationId, startDate: currentStartDate, endDate: currentEndDate });
  } catch {
    clearPendingOptimisticAllocation(activeAllocationId);
  }
}
