"use client";

import { useState, useRef, useCallback, useMemo } from "react";
import {
  DndContext,
  DragStartEvent,
  DragEndEvent,
  DragOverlay,
  PointerSensor,
  useSensor,
  useSensors,
} from "@dnd-kit/core";
import {
  addDays,
  startOfWeek,
  startOfDay,
  format,
  differenceInDays,
  isBefore,
  parseISO,
} from "date-fns";
import { scheduleTask, unscheduleTask } from "@/actions/schedule";
import { ScheduleTimeline } from "@/components/schedule/ScheduleTimeline";
import { BacklogPanel } from "@/components/schedule/BacklogPanel";
import { ScheduleFilters } from "@/components/schedule/ScheduleFilters";
import { CalendarDays, PanelRightOpen, PanelRightClose, Columns2, Rows } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useToast } from "@/components/ui/use-toast";
import { TaskStatus, TaskType } from "@prisma/client";

export const DAY_WIDTH = 216;
export const ROW_HEIGHT = 56;
export const HEADER_HEIGHT = 44;
export const LABEL_WIDTH = 208;
export const NUM_DAYS = 35;
export const SLOT_HEIGHT = 48; // task height = ROW_HEIGHT - 8

export interface ScheduleTask {
  id: string;
  title: string;
  type: TaskType;
  status: TaskStatus;
  priority: string;
  dueDate: string | null;
  estimatedHours: number | null;
  scheduledStartDate: string | null;
  scheduledEndDate: string | null;
  scheduleNotes: string | null;
  assignedArtistId: string | null;
  assignedArtist: {
    id: string;
    name: string | null;
    email: string;
    image: string | null;
  } | null;
  shot: { id: string; shotCode: string; thumbnailUrl: string | null } | null;
  asset: { id: string; assetCode: string; name: string } | null;
  project: { id: string; name: string; code: string };
}

export interface ScheduleArtist {
  id: string;
  name: string | null;
  email: string;
  image: string | null;
  role: string;
}

export interface ActiveDragData {
  type: "scheduled" | "backlog" | "resize";
  taskId: string;
  duration?: number;
  estimatedHours?: number | null;
  originalEndDate?: string;
}

interface SchedulePageClientProps {
  artists: ScheduleArtist[];
  tasks: ScheduleTask[];
  backlog: ScheduleTask[];
  projects: { id: string; name: string; code: string }[];
  canEdit: boolean;
  currentUserId: string;
  activeProject?: string;
  activeArtist?: string;
}

function toDate(val: string | null | undefined): Date | null {
  if (!val) return null;
  try {
    return parseISO(val);
  } catch {
    return new Date(val);
  }
}

function calcDuration(task: ScheduleTask): number {
  return Math.max(1, Math.ceil((task.estimatedHours ?? 8) / 8));
}

export function SchedulePageClient({
  artists,
  tasks,
  backlog,
  projects,
  canEdit,
  currentUserId,
  activeProject,
  activeArtist,
}: SchedulePageClientProps) {
  const { toast } = useToast();
  const [localTasks, setLocalTasks] = useState<ScheduleTask[]>(tasks);
  const [localBacklog, setLocalBacklog] = useState<ScheduleTask[]>(backlog);
  const [viewStart, setViewStart] = useState<Date>(() =>
    startOfWeek(new Date(), { weekStartsOn: 1 })
  );
  const [showBacklog, setShowBacklog] = useState(true);
  const [filterProject, setFilterProject] = useState(activeProject ?? "");
  const [filterArtist, setFilterArtist] = useState(activeArtist ?? "");
  const [filterStatus, setFilterStatus] = useState("");
  const [activeDrag, setActiveDrag] = useState<ActiveDragData | null>(null);
  const [resizePreview, setResizePreview] = useState<{
    taskId: string;
    endDate: Date;
    estimatedHours: number;
  } | null>(null);
  const [dayWidth, setDayWidth] = useState(DAY_WIDTH);
  const [rowHeight, setRowHeight] = useState(ROW_HEIGHT);

  const timelineRef = useRef<HTMLDivElement>(null);

  const sensors = useSensors(
    useSensor(PointerSensor, {
      activationConstraint: { distance: 6 },
    })
  );

  const days = useMemo(
    () => Array.from({ length: NUM_DAYS }, (_, i) => addDays(viewStart, i)),
    [viewStart]
  );

  const filteredArtists = useMemo(() => {
    if (!filterArtist) return artists;
    return artists.filter((a) => a.id === filterArtist);
  }, [artists, filterArtist]);

  const filteredScheduledTasks = useMemo(() => {
    return localTasks.filter((t) => {
      if (filterProject && t.project.id !== filterProject) return false;
      if (filterArtist && t.assignedArtistId !== filterArtist) return false;
      if (filterStatus && t.status !== filterStatus) return false;
      return true;
    });
  }, [localTasks, filterProject, filterArtist, filterStatus]);

  const filteredBacklog = useMemo(() => {
    return localBacklog.filter((t) => {
      if (filterProject && t.project.id !== filterProject) return false;
      if (filterArtist && t.assignedArtistId !== filterArtist) return false;
      if (filterStatus && t.status !== filterStatus) return false;
      return true;
    });
  }, [localBacklog, filterProject, filterArtist, filterStatus]);

  const handleDragStart = useCallback(
    (event: DragStartEvent) => {
      const data = event.active.data.current as ActiveDragData;
      setActiveDrag(data);
    },
    []
  );

  const handleDragEnd = useCallback(
    (event: DragEndEvent) => {
      setActiveDrag(null);
      const { active, delta, over } = event;
      if (!active.data.current) return;

      const dragData = active.data.current as ActiveDragData;

      // Resize handle - only deltaX matters
      if (dragData.type === "resize") {
        const task = localTasks.find((t) => t.id === dragData.taskId);
        if (!task?.scheduledStartDate || !dragData.originalEndDate) return;

        const originalEnd = toDate(dragData.originalEndDate)!;
        const daysDelta = Math.round(delta.x / dayWidth);
        let newEnd = addDays(originalEnd, daysDelta);

        const startDate = toDate(task.scheduledStartDate)!;
        if (isBefore(newEnd, startDate)) newEnd = startDate;

        const prevEnd = task.scheduledEndDate;
        setLocalTasks((prev) =>
          prev.map((t) =>
            t.id === dragData.taskId
              ? { ...t, scheduledEndDate: newEnd.toISOString() }
              : t
          )
        );

        scheduleTask({
          taskId: dragData.taskId,
          scheduledStartDate: task.scheduledStartDate,
          scheduledEndDate: newEnd.toISOString(),
        }).catch(() => {
          setLocalTasks((prev) =>
            prev.map((t) =>
              t.id === dragData.taskId
                ? { ...t, scheduledEndDate: prevEnd }
                : t
            )
          );
          toast({ title: "Failed to resize task", variant: "destructive" });
        });
        return;
      }

      // Check if dropped over the backlog panel (unschedule)
      if (over?.id === "backlog-drop-zone" && dragData.type === "scheduled") {
        const task = localTasks.find((t) => t.id === dragData.taskId);
        if (!task) return;

        setLocalTasks((prev) => prev.filter((t) => t.id !== dragData.taskId));
        setLocalBacklog((prev) => [
          {
            ...task,
            scheduledStartDate: null,
            scheduledEndDate: null,
          },
          ...prev,
        ]);

        unscheduleTask(dragData.taskId).catch(() => {
          setLocalTasks((prev) => [...prev, task]);
          setLocalBacklog((prev) =>
            prev.filter((t) => t.id !== dragData.taskId)
          );
          toast({ title: "Failed to unschedule task", variant: "destructive" });
        });
        return;
      }

      // Drop on timeline - calculate position from translated rect
      const translatedRect = active.rect.current.translated;
      if (!translatedRect || !timelineRef.current) return;

      const timelineBounds = timelineRef.current.getBoundingClientRect();
      const scrollLeft = timelineRef.current.scrollLeft;

      const centerX = translatedRect.left + translatedRect.width / 2;
      const centerY = translatedRect.top + translatedRect.height / 2;

      // Check if drop is within timeline bounds
      const inTimeline =
        centerX >= timelineBounds.left &&
        centerX <= timelineBounds.right &&
        centerY >= timelineBounds.top + HEADER_HEIGHT &&
        centerY <= timelineBounds.bottom;

      if (!inTimeline && dragData.type !== "backlog") return;
      if (!inTimeline && dragData.type === "backlog") return;

      const dayIndex = Math.max(
        0,
        Math.min(
          Math.floor((centerX - timelineBounds.left + scrollLeft) / dayWidth),
          NUM_DAYS - 1
        )
      );
      const artistIndex = Math.max(
        0,
        Math.min(
          Math.floor(
            (centerY - timelineBounds.top - HEADER_HEIGHT) / rowHeight
          ),
          filteredArtists.length - 1
        )
      );

      const newArtist = filteredArtists[artistIndex];
      if (!newArtist) return;

      const taskId = dragData.taskId;

      // Duration always derived from estimatedHours so width = hours
      let duration = 1;
      if (dragData.type === "scheduled") {
        const task = localTasks.find((t) => t.id === taskId);
        if (task) duration = calcDuration(task);
      } else if (dragData.type === "backlog") {
        const task = localBacklog.find((t) => t.id === taskId);
        if (task) duration = calcDuration(task);
      }

      // Pack task within the dropped day using hour offsets.
      // Sum hours already scheduled for this artist on that day,
      // and start the new task at that hour (stored in scheduledStartDate's time component).
      const existingForArtist = localTasks.filter(
        (t) =>
          t.assignedArtistId === newArtist.id &&
          t.id !== taskId &&
          t.scheduledStartDate
      );

      const droppedDay = startOfDay(addDays(viewStart, dayIndex));
      const hoursOnDay = existingForArtist
        .filter((t) => {
          const tStart = toDate(t.scheduledStartDate!);
          return tStart && differenceInDays(startOfDay(tStart), droppedDay) === 0;
        })
        .reduce((sum, t) => sum + (t.estimatedHours ?? 8), 0);

      const newStartDate = new Date(droppedDay);
      newStartDate.setHours(hoursOnDay);
      const newEndDate = addDays(newStartDate, duration - 1);

      if (dragData.type === "backlog") {
        const task = localBacklog.find((t) => t.id === taskId);
        if (!task) return;

        const scheduledTask: ScheduleTask = {
          ...task,
          scheduledStartDate: newStartDate.toISOString(),
          scheduledEndDate: newEndDate.toISOString(),
          assignedArtistId: newArtist.id,
          assignedArtist: {
            id: newArtist.id,
            name: newArtist.name,
            email: newArtist.email,
            image: newArtist.image,
          },
        };

        setLocalBacklog((prev) => prev.filter((t) => t.id !== taskId));
        setLocalTasks((prev) => [...prev, scheduledTask]);

        scheduleTask({
          taskId,
          scheduledStartDate: newStartDate.toISOString(),
          scheduledEndDate: newEndDate.toISOString(),
          assignedArtistId: newArtist.id,
        }).catch(() => {
          setLocalTasks((prev) => prev.filter((t) => t.id !== taskId));
          setLocalBacklog((prev) => [task, ...prev]);
          toast({ title: "Failed to schedule task", variant: "destructive" });
        });
      } else if (dragData.type === "scheduled") {
        const prevTask = localTasks.find((t) => t.id === taskId);

        setLocalTasks((prev) =>
          prev.map((t) =>
            t.id === taskId
              ? {
                  ...t,
                  scheduledStartDate: newStartDate.toISOString(),
                  scheduledEndDate: newEndDate.toISOString(),
                  assignedArtistId: newArtist.id,
                  assignedArtist: {
                    id: newArtist.id,
                    name: newArtist.name,
                    email: newArtist.email,
                    image: newArtist.image,
                  },
                }
              : t
          )
        );

        scheduleTask({
          taskId,
          scheduledStartDate: newStartDate.toISOString(),
          scheduledEndDate: newEndDate.toISOString(),
          assignedArtistId: newArtist.id,
        }).catch(() => {
          if (prevTask) {
            setLocalTasks((prev) =>
              prev.map((t) => (t.id === taskId ? prevTask : t))
            );
          }
          toast({ title: "Failed to move task", variant: "destructive" });
        });
      }
    },
    [localTasks, localBacklog, viewStart, filteredArtists, toast, dayWidth, rowHeight]
  );

  const handleResizeMouseDown = useCallback(
    (taskId: string, _currentEndDate: string) =>
      (e: React.MouseEvent) => {
        e.preventDefault();
        e.stopPropagation();

        const prevTask = localTasks.find((t) => t.id === taskId);
        if (!prevTask?.scheduledStartDate) return;

        const startX = e.clientX;
        const startDate = toDate(prevTask.scheduledStartDate)!;
        const originalHours = prevTask.estimatedHours ?? 8;
        const HOUR_WIDTH = dayWidth / 8; // px per 1 hour
        let currentHours = originalHours;

        const onMouseMove = (me: MouseEvent) => {
          const deltaPx = me.clientX - startX;
          const hoursDelta = Math.round(deltaPx / HOUR_WIDTH);
          currentHours = Math.max(1, originalHours + hoursDelta);
          const newDays = Math.max(1, Math.ceil(currentHours / 8));
          const newEndDate = addDays(startDate, newDays - 1);
          setResizePreview({
            taskId,
            endDate: newEndDate,
            estimatedHours: currentHours,
          });
        };

        const onMouseUp = () => {
          window.removeEventListener("mousemove", onMouseMove);
          window.removeEventListener("mouseup", onMouseUp);

          const newDays = Math.max(1, Math.ceil(currentHours / 8));
          const finalEnd = addDays(startDate, newDays - 1);

          setResizePreview(null);
          setLocalTasks((prev) =>
            prev.map((t) =>
              t.id === taskId
                ? {
                    ...t,
                    scheduledEndDate: finalEnd.toISOString(),
                    estimatedHours: currentHours,
                  }
                : t
            )
          );

          scheduleTask({
            taskId,
            scheduledStartDate: prevTask.scheduledStartDate,
            scheduledEndDate: finalEnd.toISOString(),
            estimatedHours: currentHours,
          }).catch(() => {
            setLocalTasks((prev) =>
              prev.map((t) => (t.id === taskId ? prevTask : t))
            );
            toast({ title: "Failed to resize task", variant: "destructive" });
          });
        };

        window.addEventListener("mousemove", onMouseMove);
        window.addEventListener("mouseup", onMouseUp);
      },
    [localTasks, toast, dayWidth]
  );

  const activeDragTask = activeDrag
    ? localTasks.find((t) => t.id === activeDrag.taskId) ??
      localBacklog.find((t) => t.id === activeDrag.taskId)
    : null;

  const handleUnschedule = useCallback(
    (taskId: string) => {
      const task = localTasks.find((t) => t.id === taskId);
      if (!task) return;
      setLocalTasks((prev) => prev.filter((t) => t.id !== taskId));
      setLocalBacklog((prev) => [
        { ...task, scheduledStartDate: null, scheduledEndDate: null },
        ...prev,
      ]);
      unscheduleTask(taskId).catch(() => {
        setLocalTasks((prev) => [...prev, task]);
        setLocalBacklog((prev) => prev.filter((t) => t.id !== taskId));
        toast({ title: "Failed to unschedule task", variant: "destructive" });
      });
    },
    [localTasks, toast]
  );

  return (
    <DndContext
      sensors={sensors}
      onDragStart={handleDragStart}
      onDragEnd={handleDragEnd}
    >
      <div className="flex flex-col h-full overflow-hidden bg-zinc-950">
        {/* Header */}
        <div className="flex items-center justify-between px-6 py-3 border-b border-zinc-800 bg-zinc-900 shrink-0">
          <div className="flex items-center gap-2">
            <CalendarDays className="h-5 w-5 text-amber-400" />
            <h1 className="text-lg font-semibold text-white">Schedule</h1>
            <span className="text-sm text-zinc-500">
              {format(viewStart, "MMM d")} —{" "}
              {format(addDays(viewStart, NUM_DAYS - 1), "MMM d, yyyy")}
            </span>
          </div>
          <div className="flex items-center gap-2">
            {/* Zoom controls */}
            <div className="flex items-center gap-3 border border-zinc-700 rounded-md px-2.5 py-1">
              <div className="flex items-center gap-1.5">
                <Columns2 className="h-3 w-3 text-zinc-500" />
                <input
                  type="range"
                  min={60}
                  max={400}
                  step={8}
                  value={dayWidth}
                  onChange={(e) => setDayWidth(Number(e.target.value))}
                  className="w-20 accent-amber-400 cursor-pointer"
                />
              </div>
              <div className="flex items-center gap-1.5">
                <Rows className="h-3 w-3 text-zinc-500" />
                <input
                  type="range"
                  min={40}
                  max={120}
                  step={4}
                  value={rowHeight}
                  onChange={(e) => setRowHeight(Number(e.target.value))}
                  className="w-20 accent-amber-400 cursor-pointer"
                />
              </div>
            </div>
            <Button
              variant="ghost"
              size="sm"
              onClick={() => setShowBacklog((b) => !b)}
              className="text-zinc-400 hover:text-white gap-2"
            >
              {showBacklog ? (
                <PanelRightClose className="h-4 w-4" />
              ) : (
                <PanelRightOpen className="h-4 w-4" />
              )}
              {showBacklog ? "Hide" : "Backlog"}
            </Button>
          </div>
        </div>

        {/* Filters */}
        <ScheduleFilters
          projects={projects}
          artists={artists}
          filterProject={filterProject}
          filterArtist={filterArtist}
          filterStatus={filterStatus}
          viewStart={viewStart}
          onProjectChange={setFilterProject}
          onArtistChange={setFilterArtist}
          onStatusChange={setFilterStatus}
          onViewStartChange={setViewStart}
        />

        {/* Main content */}
        <div className="flex flex-1 overflow-hidden">
          <ScheduleTimeline
            artists={filteredArtists}
            tasks={filteredScheduledTasks}
            days={days}
            viewStart={viewStart}
            canEdit={canEdit}
            timelineRef={timelineRef}
            resizePreview={resizePreview}
            onResizeMouseDown={handleResizeMouseDown}
            activeDragId={activeDrag?.taskId ?? null}
            onUnschedule={handleUnschedule}
            dayWidth={dayWidth}
            rowHeight={rowHeight}
          />

          {showBacklog && (
            <BacklogPanel
              tasks={filteredBacklog}
              canEdit={canEdit}
            />
          )}
        </div>

        {/* Drag overlay */}
        <DragOverlay dropAnimation={null}>
          {activeDragTask && (
            <div className="rounded-md border border-amber-500/60 bg-amber-500/20 px-2 py-1.5 text-xs font-medium text-amber-200 shadow-xl opacity-90 max-w-[200px] truncate pointer-events-none">
              {activeDragTask.shot?.shotCode ??
                activeDragTask.asset?.assetCode ??
                activeDragTask.title}
            </div>
          )}
        </DragOverlay>
      </div>
    </DndContext>
  );
}
