import { useState, useCallback, useRef, type PointerEvent, type DragEvent } from "react";
import { format, parseISO } from "date-fns";
import { useAuthStore } from "@/stores/auth-store";
import { useCalendarStore } from "@/stores/calendar-store";
import { toast } from "@/stores/toast-store";
import { debug } from "@/lib/debug";
import { formatIsoInTimeZone } from "@/lib/calendar-utils";
import type { Calendar } from "@/lib/jmap/types";

interface DragCreateState {
  dayKey: string;
  startMinutes: number;
  endMinutes: number;
}

export type ResizeEdge = "top" | "bottom";

interface ResizeState {
  eventId: string;
  topPx: number;
  heightPx: number;
  startMinutes: number;
  durationMinutes: number;
}

export interface QuickCreateState {
  dayKey: string;
  day: Date;
  hour: number;
  top: number;
}

interface DropTargetState {
  dayKey: string;
  minutes: number;
}

interface UseTimeGridInteractionsOptions {
  hourHeight: number;
  calendars: Calendar[];
  onCreateRange: (startDate: Date, endDate?: Date) => void;
  errorMessages: {
    resize: string;
    move: string;
    created: string;
    error: string;
  };
  isMobile?: boolean;
}

export function useTimeGridInteractions({
  hourHeight,
  calendars,
  onCreateRange,
  errorMessages,
  isMobile,
}: UseTimeGridInteractionsOptions) {
  const snapToMinutes = useCallback((clientY: number, containerTop: number): number => {
    const raw = ((clientY - containerTop) / hourHeight) * 60;
    return Math.max(0, Math.min(1440, Math.round(raw / 15) * 15));
  }, [hourHeight]);

  const wasDragging = useRef(false);

  // --- Drag-to-create ---
  const dragRef = useRef<{
    dayKey: string;
    dayDate: Date;
    startMinutes: number;
    pointerId: number;
    startY: number;
    captured: boolean;
  } | null>(null);

  const [dragCreate, setDragCreate] = useState<DragCreateState | null>(null);

  const handleGridPointerDown = useCallback((
    e: PointerEvent<HTMLDivElement>,
    dayKey: string,
    dayDate: Date,
  ) => {
    if (isMobile) return;
    if (e.pointerType === "touch") return;
    if (e.button !== 0) return;
    if ((e.target as HTMLElement).closest("[data-calendar-event], [data-resize-handle]")) return;

    const rect = e.currentTarget.getBoundingClientRect();
    const minutes = snapToMinutes(e.clientY, rect.top);

    dragRef.current = {
      dayKey, dayDate, startMinutes: minutes,
      pointerId: e.pointerId, startY: e.clientY, captured: false,
    };
  }, [snapToMinutes, isMobile]);

  const handleGridPointerMove = useCallback((e: PointerEvent<HTMLDivElement>) => {
    if (!dragRef.current) return;

    if (!dragRef.current.captured) {
      if (Math.abs(e.clientY - dragRef.current.startY) < 5) return;
      dragRef.current.captured = true;
      e.currentTarget.setPointerCapture(dragRef.current.pointerId);
    }

    const rect = e.currentTarget.getBoundingClientRect();
    const currentMinutes = snapToMinutes(e.clientY, rect.top);
    const start = Math.min(dragRef.current.startMinutes, currentMinutes);
    const end = Math.max(dragRef.current.startMinutes, currentMinutes);

    setDragCreate(end > start ? { dayKey: dragRef.current.dayKey, startMinutes: start, endMinutes: end } : null);
  }, [snapToMinutes]);

  const handleGridPointerUp = useCallback((e: PointerEvent<HTMLDivElement>) => {
    const drag = dragRef.current;
    dragRef.current = null;
    setDragCreate(null);

    if (!drag || !drag.captured) return;

    wasDragging.current = true;
    requestAnimationFrame(() => { wasDragging.current = false; });

    try { e.currentTarget.releasePointerCapture(drag.pointerId); } catch { /* may already be released */ }

    const rect = e.currentTarget.getBoundingClientRect();
    const endMinutes = snapToMinutes(e.clientY, rect.top);
    const start = Math.min(drag.startMinutes, endMinutes);
    const end = Math.max(drag.startMinutes, endMinutes);

    if (end - start < 15) return;

    const startDate = new Date(drag.dayDate);
    startDate.setHours(Math.floor(start / 60), start % 60, 0, 0);
    const endDate = new Date(drag.dayDate);
    endDate.setHours(Math.floor(end / 60), end % 60, 0, 0);
    onCreateRange(startDate, endDate);
  }, [snapToMinutes, onCreateRange]);

  // --- Resize ---
  const resizeRef = useRef<{
    eventId: string;
    edge: ResizeEdge;
    startY: number;
    originalStartMinutes: number;
    originalDurationMinutes: number;
    pointerId: number;
  } | null>(null);

  const [resizeVisual, setResizeVisual] = useState<ResizeState | null>(null);

  const computeResize = useCallback((
    ref: NonNullable<typeof resizeRef.current>,
    clientY: number,
  ): { startMinutes: number; durationMinutes: number } => {
    const deltaY = clientY - ref.startY;
    const deltaMinutes = Math.round((deltaY / hourHeight) * 60 / 15) * 15;
    const originalEnd = ref.originalStartMinutes + ref.originalDurationMinutes;

    if (ref.edge === "bottom") {
      const newDuration = Math.max(15, ref.originalDurationMinutes + deltaMinutes);
      return { startMinutes: ref.originalStartMinutes, durationMinutes: newDuration };
    }
    // Top edge: move start, keep end fixed. Clamp so duration stays >= 15 and start >= 0.
    let newStart = ref.originalStartMinutes + deltaMinutes;
    newStart = Math.max(0, Math.min(originalEnd - 15, newStart));
    return { startMinutes: newStart, durationMinutes: originalEnd - newStart };
  }, [hourHeight]);

  const handleResizePointerDown = useCallback((
    eventId: string,
    edge: ResizeEdge,
    originalStartMinutes: number,
    originalDurationMinutes: number,
    e: PointerEvent,
  ) => {
    e.stopPropagation();
    e.preventDefault();

    resizeRef.current = {
      eventId,
      edge,
      startY: e.clientY,
      originalStartMinutes,
      originalDurationMinutes,
      pointerId: e.pointerId,
    };
    (e.target as HTMLElement).setPointerCapture(e.pointerId);
  }, []);

  const handleResizePointerMove = useCallback((e: PointerEvent) => {
    if (!resizeRef.current) return;
    const { startMinutes, durationMinutes } = computeResize(resizeRef.current, e.clientY);
    setResizeVisual({
      eventId: resizeRef.current.eventId,
      topPx: (startMinutes / 60) * hourHeight,
      heightPx: (durationMinutes / 60) * hourHeight,
      startMinutes,
      durationMinutes,
    });
  }, [hourHeight, computeResize]);

  const handleResizePointerUp = useCallback(async (e: PointerEvent) => {
    const resize = resizeRef.current;
    resizeRef.current = null;

    if (!resize) return;

    wasDragging.current = true;
    requestAnimationFrame(() => { wasDragging.current = false; });

    try { (e.target as HTMLElement).releasePointerCapture(resize.pointerId); } catch { /* may already be released */ }

    const { startMinutes: newStartMinutes, durationMinutes: newDurationMinutes } = computeResize(resize, e.clientY);

    const startChanged = newStartMinutes !== resize.originalStartMinutes;
    const durationChanged = newDurationMinutes !== resize.originalDurationMinutes;
    if (!startChanged && !durationChanged) {
      setResizeVisual(null);
      return;
    }

    const hours = Math.floor(newDurationMinutes / 60);
    const mins = newDurationMinutes % 60;
    let dur = "PT";
    if (hours > 0) dur += `${hours}H`;
    if (mins > 0) dur += `${mins}M`;
    if (dur === "PT") dur = "PT0M";

    const client = useAuthStore.getState().client;
    if (!client) {
      setResizeVisual(null);
      toast.error(errorMessages.resize);
      return;
    }

    try {
      const event = useCalendarStore.getState().events.find(ev => ev.id === resize.eventId);
      const hasParticipants = event?.participants && Object.keys(event.participants).length > 0;
      const updates: { start?: string; duration: string } = { duration: dur };
      if (startChanged && event?.start) {
        // Shift the event's floating `start` wall-clock by the delta (preserves event.timeZone).
        const deltaMinutes = newStartMinutes - resize.originalStartMinutes;
        const shifted = new Date(parseISO(event.start).getTime() + deltaMinutes * 60000);
        updates.start = format(shifted, "yyyy-MM-dd'T'HH:mm:ss");
      }
      await useCalendarStore.getState().updateEvent(client, resize.eventId, updates, hasParticipants || undefined);
    } catch (error) {
      debug.error("Failed to resize event:", resize.eventId, error);
      toast.error(errorMessages.resize);
    } finally {
      setResizeVisual(null);
    }
  }, [computeResize, errorMessages.resize]);

  // --- Click / Double-click / Quick-create ---
  const clickTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const [quickCreate, setQuickCreate] = useState<QuickCreateState | null>(null);

  const handleSlotClick = useCallback((day: Date, hour: number) => {
    if (wasDragging.current) return;
    if (clickTimerRef.current) {
      clearTimeout(clickTimerRef.current);
      clickTimerRef.current = null;
      return;
    }
    clickTimerRef.current = setTimeout(() => {
      clickTimerRef.current = null;
      const d = new Date(day);
      d.setHours(hour, 0, 0, 0);
      onCreateRange(d);
    }, 250);
  }, [onCreateRange]);

  const handleSlotDoubleClick = useCallback((day: Date, hour: number) => {
    if (wasDragging.current) return;
    if (clickTimerRef.current) {
      clearTimeout(clickTimerRef.current);
      clickTimerRef.current = null;
    }
    const d = new Date(day);
    d.setHours(hour, 0, 0, 0);
    onCreateRange(d);
  }, [onCreateRange]);

  const handleQuickCreateSubmit = useCallback(async (title: string) => {
    if (!quickCreate) return;
    const client = useAuthStore.getState().client;
    if (!client) {
      setQuickCreate(null);
      toast.error(errorMessages.error);
      return;
    }
    try {
      const startDate = new Date(quickCreate.day);
      startDate.setHours(quickCreate.hour, 0, 0, 0);
      const startStr = format(startDate, "yyyy-MM-dd'T'HH:mm:ss");
      const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
      const defaultCal = calendars.find(c => c.isDefault) || calendars[0];
      const created = await useCalendarStore.getState().createEvent(client, {
        title,
        start: startStr,
        duration: "PT1H",
        timeZone,
        calendarIds: defaultCal ? { [defaultCal.id]: true } : {},
        status: "confirmed",
        freeBusyStatus: "busy",
        privacy: "public",
      });
      setQuickCreate(null);
      if (created) toast.success(errorMessages.created);
      else toast.error(errorMessages.error);
    } catch (error) {
      debug.error("Failed to quick-create event:", error);
      setQuickCreate(null);
      toast.error(errorMessages.error);
    }
  }, [quickCreate, calendars, errorMessages.created, errorMessages.error]);

  const handleQuickCreateCancel = useCallback(() => {
    setQuickCreate(null);
  }, []);

  // --- DnD drop ---
  const [dropTarget, setDropTarget] = useState<DropTargetState | null>(null);

  const snapDragMinutes = useCallback((e: DragEvent<HTMLDivElement>): number => {
    const rect = e.currentTarget.getBoundingClientRect();
    const y = e.clientY - rect.top;
    const raw = (y / hourHeight) * 60;
    return Math.max(0, Math.min(1425, Math.round(raw / 15) * 15));
  }, [hourHeight]);

  const handleColumnDragOver = useCallback((e: DragEvent<HTMLDivElement>, dayKey: string) => {
    if (!e.dataTransfer.types.includes("application/x-calendar-event")) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = "move";
    const minutes = snapDragMinutes(e);
    setDropTarget((prev) =>
      prev?.dayKey === dayKey && prev?.minutes === minutes ? prev : { dayKey, minutes }
    );
  }, [snapDragMinutes]);

  const handleColumnDragLeave = useCallback((e: DragEvent<HTMLDivElement>) => {
    const related = e.relatedTarget as Node | null;
    if (!e.currentTarget.contains(related)) setDropTarget(null);
  }, []);

  const handleColumnDrop = useCallback(async (e: DragEvent<HTMLDivElement>, day: Date) => {
    e.preventDefault();
    setDropTarget(null);
    const json = e.dataTransfer.getData("application/x-calendar-event");
    if (!json) return;
    try {
      const data = JSON.parse(json);
      const minutes = snapDragMinutes(e);
      const newStart = new Date(day);
      newStart.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0);
      const event = useCalendarStore.getState().events.find(ev => ev.id === data.eventId);
      // Emit `start` as a floating wall-clock in the event's own timeZone.
      // If we used browser-local, the server would reinterpret it in event.timeZone
      // and shift the event by the offset between the two zones.
      const eventTimeZone = event?.timeZone || Intl.DateTimeFormat().resolvedOptions().timeZone;
      const newStartISO = formatIsoInTimeZone(newStart, eventTimeZone);
      if (newStartISO === data.originalStart) return;
      const client = useAuthStore.getState().client;
      if (!client) {
        toast.error(errorMessages.move);
        return;
      }
      const hasParticipants = event?.participants && Object.keys(event.participants).length > 0;
      await useCalendarStore.getState().updateEvent(client, data.eventId, { start: newStartISO }, hasParticipants || undefined);
    } catch {
      toast.error(errorMessages.move);
    }
  }, [snapDragMinutes, errorMessages.move]);

  return {
    dragCreate,
    handleGridPointerDown,
    handleGridPointerMove,
    handleGridPointerUp,
    resizeVisual,
    handleResizePointerDown,
    handleResizePointerMove,
    handleResizePointerUp,
    wasDragging,
    quickCreate,
    handleSlotClick,
    handleSlotDoubleClick,
    handleQuickCreateSubmit,
    handleQuickCreateCancel,
    dropTarget,
    handleColumnDragOver,
    handleColumnDragLeave,
    handleColumnDrop,
  };
}
