"use client";

import { clsx } from "clsx";
import { useEffect, useRef, useState } from "react";
import { useAllocationHistory } from "~/hooks/useAllocationHistory.js";
import { useProjectDragContext } from "~/hooks/useProjectDragContext.js";
import { useTimelineDrag } from "~/hooks/useTimelineDrag.js";
import { useTimelineLayout } from "~/hooks/useTimelineLayout.js";
import { FillOpenDemandModal } from "~/components/allocations/FillOpenDemandModal.js";
import { AllocationPopover } from "./AllocationPopover.js";
import { NewAllocationPopover } from "./NewAllocationPopover.js";
import { ProjectPanel } from "./ProjectPanel.js";
import { ShiftPreviewTooltip } from "./ShiftPreviewTooltip.js";
import { TimelineHeader } from "./TimelineHeader.js";
import { TimelineToolbar } from "./TimelineToolbar.js";
import { addDays } from "./utils.js";
import { HEADER_DAY_HEIGHT, HEADER_MONTH_HEIGHT, LABEL_WIDTH } from "./timelineConstants.js";
import { formatDateShort } from "~/lib/format.js";
import {
  TimelineProvider,
  useTimelineContext,
  type TimelineAssignmentEntry,
} from "./TimelineContext.js";
import { TimelineResourcePanel } from "./TimelineResourcePanel.js";
import { TimelineProjectPanel, type OpenDemandAssignment } from "./TimelineProjectPanel.js";

// ─── Entry point ────────────────────────────────────────────────────────────
// Two-layer mount: the outer shell creates drag state + project context,
// then wraps children with TimelineProvider. The inner content consumes context.

export function TimelineView() {
  const mousePosRef = useRef({ x: 0, y: 0 });

  const { push: pushHistory, undo, redo, canUndo, canRedo } = useAllocationHistory();
  const pushHistoryRef = useRef(pushHistory);
  pushHistoryRef.current = pushHistory;

  const [popover, setPopover] = useState<{
    allocationId: string;
    projectId: string;
    x: number;
    y: number;
  } | null>(null);
  const [newAllocPopover, setNewAllocPopover] = useState<{
    resourceId: string;
    startDate: Date;
    endDate: Date;
    suggestedProjectId: string | null;
    anchorX: number;
    anchorY: number;
  } | null>(null);

  // cellWidth placeholder — the real value comes from useTimelineLayout inside the content.
  // useTimelineDrag only needs cellWidth for pixel→day conversion during drag.
  // We start with 40 (day zoom default) and update via a ref.
  const cellWidthRef = useRef(40);

  const {
    dragState,
    allocDragState,
    rangeState,
    shiftPreview,
    isPreviewLoading,
    isApplying,
    isAllocSaving,
    onProjectBarMouseDown,
    onAllocMouseDown,
    onRowMouseDown,
    onCanvasMouseMove,
    onCanvasMouseUp,
    onCanvasMouseLeave,
    onProjectBarTouchStart,
    onAllocTouchStart,
    onRowTouchStart,
    onCanvasTouchMove,
    onCanvasTouchEnd,
  } = useTimelineDrag({
    cellWidth: cellWidthRef.current,
    onBlockClick: (info) => {
      setPopover({
        allocationId: info.allocationId,
        projectId: info.projectId,
        x: mousePosRef.current.x,
        y: mousePosRef.current.y,
      });
    },
    onRangeSelected: (info) => {
      setNewAllocPopover({
        resourceId: info.resourceId,
        startDate: info.startDate,
        endDate: info.endDate,
        suggestedProjectId: info.suggestedProjectId,
        anchorX: info.anchorX,
        anchorY: info.anchorY,
      });
    },
    onAllocationMoved: (snapshot) => {
      pushHistoryRef.current(snapshot);
    },
  });

  const [openPanelProjectId, setOpenPanelProjectId] = useState<string | null>(null);
  const dragProjectId = dragState.isDragging ? dragState.projectId : null;
  const contextProjectId = dragProjectId ?? openPanelProjectId;
  const { contextResourceIds, contextAllocations } = useProjectDragContext(contextProjectId);

  return (
    <TimelineProvider
      isDragging={dragState.isDragging}
      contextAllocations={contextAllocations as TimelineAssignmentEntry[]}
    >
      <TimelineViewContent
        mousePosRef={mousePosRef}
        cellWidthRef={cellWidthRef}
        dragState={dragState}
        allocDragState={allocDragState}
        rangeState={rangeState}
        shiftPreview={shiftPreview}
        isPreviewLoading={isPreviewLoading}
        isApplying={isApplying}
        isAllocSaving={isAllocSaving}
        onProjectBarMouseDown={onProjectBarMouseDown}
        onAllocMouseDown={onAllocMouseDown}
        onRowMouseDown={onRowMouseDown}
        onCanvasMouseMove={onCanvasMouseMove}
        onCanvasMouseUp={onCanvasMouseUp}
        onCanvasMouseLeave={onCanvasMouseLeave}
        onProjectBarTouchStart={onProjectBarTouchStart}
        onAllocTouchStart={onAllocTouchStart}
        onRowTouchStart={onRowTouchStart}
        onCanvasTouchMove={onCanvasTouchMove}
        onCanvasTouchEnd={onCanvasTouchEnd}
        contextResourceIds={contextResourceIds}
        popover={popover}
        setPopover={setPopover}
        newAllocPopover={newAllocPopover}
        setNewAllocPopover={setNewAllocPopover}
        openPanelProjectId={openPanelProjectId}
        setOpenPanelProjectId={setOpenPanelProjectId}
        canUndo={canUndo}
        canRedo={canRedo}
        undo={undo}
        redo={redo}
      />
    </TimelineProvider>
  );
}

// ─── Content (inside TimelineProvider — has context access) ─────────────────

function TimelineViewContent({
  mousePosRef,
  cellWidthRef,
  dragState,
  allocDragState,
  rangeState,
  shiftPreview,
  isPreviewLoading,
  isApplying,
  isAllocSaving,
  onProjectBarMouseDown,
  onAllocMouseDown,
  onRowMouseDown,
  onCanvasMouseMove,
  onCanvasMouseUp,
  onCanvasMouseLeave,
  onProjectBarTouchStart,
  onAllocTouchStart,
  onRowTouchStart,
  onCanvasTouchMove,
  onCanvasTouchEnd,
  contextResourceIds,
  popover,
  setPopover,
  newAllocPopover,
  setNewAllocPopover,
  openPanelProjectId,
  setOpenPanelProjectId,
  canUndo,
  canRedo,
  undo,
  redo,
}: {
  mousePosRef: React.RefObject<{ x: number; y: number }>;
  cellWidthRef: React.RefObject<number>;
  dragState: ReturnType<typeof useTimelineDrag>["dragState"];
  allocDragState: ReturnType<typeof useTimelineDrag>["allocDragState"];
  rangeState: ReturnType<typeof useTimelineDrag>["rangeState"];
  shiftPreview: ReturnType<typeof useTimelineDrag>["shiftPreview"];
  isPreviewLoading: boolean;
  isApplying: boolean;
  isAllocSaving: boolean;
  onProjectBarMouseDown: ReturnType<typeof useTimelineDrag>["onProjectBarMouseDown"];
  onAllocMouseDown: ReturnType<typeof useTimelineDrag>["onAllocMouseDown"];
  onRowMouseDown: ReturnType<typeof useTimelineDrag>["onRowMouseDown"];
  onCanvasMouseMove: ReturnType<typeof useTimelineDrag>["onCanvasMouseMove"];
  onCanvasMouseUp: ReturnType<typeof useTimelineDrag>["onCanvasMouseUp"];
  onCanvasMouseLeave: ReturnType<typeof useTimelineDrag>["onCanvasMouseLeave"];
  onProjectBarTouchStart: ReturnType<typeof useTimelineDrag>["onProjectBarTouchStart"];
  onAllocTouchStart: ReturnType<typeof useTimelineDrag>["onAllocTouchStart"];
  onRowTouchStart: ReturnType<typeof useTimelineDrag>["onRowTouchStart"];
  onCanvasTouchMove: ReturnType<typeof useTimelineDrag>["onCanvasTouchMove"];
  onCanvasTouchEnd: ReturnType<typeof useTimelineDrag>["onCanvasTouchEnd"];
  contextResourceIds: string[];
  popover: { allocationId: string; projectId: string; x: number; y: number } | null;
  setPopover: React.Dispatch<React.SetStateAction<typeof popover>>;
  newAllocPopover: {
    resourceId: string;
    startDate: Date;
    endDate: Date;
    suggestedProjectId: string | null;
    anchorX: number;
    anchorY: number;
  } | null;
  setNewAllocPopover: React.Dispatch<React.SetStateAction<typeof newAllocPopover>>;
  openPanelProjectId: string | null;
  setOpenPanelProjectId: React.Dispatch<React.SetStateAction<string | null>>;
  canUndo: boolean;
  canRedo: boolean;
  undo: () => Promise<void>;
  redo: () => Promise<void>;
}) {
  const ctx = useTimelineContext();
  const {
    resources,
    projectGroups,
    viewStart,
    viewEnd,
    viewDays,
    setViewStart,
    setViewDays,
    filters,
    setFilters,
    filterOpen,
    setFilterOpen,
    viewMode,
    setViewMode,
    today,
    isLoading,
    isInitialLoading,
    totalAllocCount,
  } = ctx;

  const scrollContainerRef = useRef<HTMLDivElement>(null);
  const canvasRef = useRef<HTMLDivElement>(null);

  // Tooltip DOM refs
  const dragTooltipRef = useRef<HTMLDivElement>(null);
  const allocTooltipRef = useRef<HTMLDivElement>(null);
  const rangeHintRef = useRef<HTMLDivElement>(null);
  const [openDemandToAssign, setOpenDemandToAssign] = useState<OpenDemandAssignment | null>(null);

  const { CELL_WIDTH, dates, totalCanvasWidth, toLeft, toWidth, gridLines, monthGroups, xToDate } =
    useTimelineLayout(viewStart, viewDays, filters.zoom, filters.showWeekends, today);
  const hasActivePointerOverlay =
    dragState.isDragging || allocDragState.isActive || rangeState.isSelecting;

  function openAllocationPopoverAt(
    info: {
      allocationId: string;
      projectId: string;
    },
    anchorX: number,
    anchorY: number,
  ) {
    setPopover({
      allocationId: info.allocationId,
      projectId: info.projectId,
      x: anchorX,
      y: anchorY,
    });
  }

  // Keep cellWidthRef in sync so the drag hook uses the correct value.
  cellWidthRef.current = CELL_WIDTH;

  // ─── Native mousemove listener — updates tooltips without React state ─────
  useEffect(() => {
    if (!hasActivePointerOverlay) return;
    const el = canvasRef.current;
    if (!el) return;
    const handler = (e: MouseEvent) => {
      const x = e.clientX;
      const y = e.clientY;
      mousePosRef.current = { x, y };
      if (dragTooltipRef.current) {
        dragTooltipRef.current.style.left = `${x + 12}px`;
        dragTooltipRef.current.style.top = `${y - 8}px`;
      }
      if (allocTooltipRef.current) {
        allocTooltipRef.current.style.left = `${x + 14}px`;
        allocTooltipRef.current.style.top = `${y - 36}px`;
      }
      if (rangeHintRef.current) {
        rangeHintRef.current.style.left = `${x + 12}px`;
        rangeHintRef.current.style.top = `${y - 28}px`;
      }
    };
    el.addEventListener("mousemove", handler, { passive: true });
    return () => el.removeEventListener("mousemove", handler);
  }, [hasActivePointerOverlay, isLoading, mousePosRef]); // eslint-disable-line react-hooks/exhaustive-deps

  // ─── Shift+wheel → horizontal scroll ──────────────────────────────────────
  useEffect(() => {
    const el = scrollContainerRef.current;
    if (!el) return;
    const handler = (e: WheelEvent) => {
      if (e.shiftKey && e.deltaY !== 0 && e.deltaX === 0) {
        e.preventDefault();
        el.scrollLeft += e.deltaY;
      }
    };
    el.addEventListener("wheel", handler, { passive: false });
    return () => el.removeEventListener("wheel", handler);
  }, [isLoading]); // eslint-disable-line react-hooks/exhaustive-deps

  // ─── Keyboard undo/redo ───────────────────────────────────────────────────
  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      const isMac = navigator.platform.toUpperCase().includes("MAC");
      const modKey = isMac ? e.metaKey : e.ctrlKey;
      if (!modKey) return;
      if (e.key === "z" && !e.shiftKey) {
        e.preventDefault();
        void undo();
      }
      if ((e.key === "z" && e.shiftKey) || e.key === "y") {
        e.preventDefault();
        void redo();
      }
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [undo, redo]);

  // ─── Lazy-extend date range on scroll ─────────────────────────────────────
  function handleContainerScroll() {
    const el = scrollContainerRef.current;
    if (!el) return;
    const distanceFromRight = el.scrollWidth - el.scrollLeft - el.clientWidth;
    if (distanceFromRight < CELL_WIDTH * 40) {
      setViewDays((d) => d + 120);
    }
  }

  const handleMouseMove = (e: React.MouseEvent) => {
    if (!hasActivePointerOverlay) return;
    onCanvasMouseMove(e);
  };

  return (
    <div className="relative flex flex-1 flex-col gap-4 min-h-0">
      {/* Toolbar */}
      <TimelineToolbar
        viewMode={viewMode}
        onViewModeChange={setViewMode}
        filters={filters}
        onFiltersChange={setFilters}
        filterOpen={filterOpen}
        onFilterOpenChange={setFilterOpen}
        resourceCount={resources.length}
        projectCount={projectGroups.length}
        totalAllocCount={totalAllocCount}
        onNavigateBack={() => setViewStart((v) => addDays(v, -28))}
        onNavigateToday={() => setViewStart(addDays(today, -30))}
        onNavigateForward={() => setViewStart((v) => addDays(v, 28))}
        canUndo={canUndo}
        canRedo={canRedo}
        onUndo={() => {
          void undo();
        }}
        onRedo={() => {
          void redo();
        }}
      />

      {/* Scrollable canvas */}
      <div
        ref={scrollContainerRef}
        onScroll={handleContainerScroll}
        className="app-surface relative flex-1 overflow-auto"
      >
        {isInitialLoading ? (
          <div className="flex items-center justify-center py-24 text-sm text-gray-500 dark:text-gray-400">
            Loading timeline...
          </div>
        ) : (
          <div style={{ minWidth: LABEL_WIDTH + totalCanvasWidth }}>
            <TimelineHeader
              monthGroups={monthGroups}
              dates={dates}
              CELL_WIDTH={CELL_WIDTH}
              LABEL_WIDTH={LABEL_WIDTH}
              HEADER_MONTH_HEIGHT={HEADER_MONTH_HEIGHT}
              HEADER_DAY_HEIGHT={HEADER_DAY_HEIGHT}
              zoom={filters.zoom}
              viewMode={viewMode}
              today={today}
            />

            {/* Canvas rows */}
            <div
              ref={canvasRef}
              onMouseMove={handleMouseMove}
              onMouseUp={(e) => void onCanvasMouseUp(e)}
              onMouseLeave={onCanvasMouseLeave}
              onTouchMove={(e) => {
                if (!hasActivePointerOverlay) return;
                onCanvasTouchMove(e);
              }}
              onTouchEnd={(e) => void onCanvasTouchEnd(e)}
              className={clsx(
                (dragState.isDragging || allocDragState.isActive) && "cursor-grabbing select-none",
                rangeState.isSelecting && "cursor-crosshair select-none",
              )}
            >
              {viewMode === "resource" && (
                <TimelineResourcePanel
                  scrollContainerRef={scrollContainerRef}
                  dragState={dragState}
                  allocDragState={allocDragState}
                  rangeState={rangeState}
                  shiftPreview={shiftPreview}
                  contextResourceIds={contextResourceIds}
                  onAllocMouseDown={onAllocMouseDown}
                  onAllocTouchStart={onAllocTouchStart}
                  onRowMouseDown={onRowMouseDown}
                  onRowTouchStart={onRowTouchStart}
                  onAllocationContextMenu={openAllocationPopoverAt}
                  CELL_WIDTH={CELL_WIDTH}
                  dates={dates}
                  totalCanvasWidth={totalCanvasWidth}
                  toLeft={toLeft}
                  toWidth={toWidth}
                  gridLines={gridLines}
                  xToDate={xToDate}
                />
              )}

              {viewMode === "project" && (
                <TimelineProjectPanel
                  scrollContainerRef={scrollContainerRef}
                  dragState={dragState}
                  allocDragState={allocDragState}
                  rangeState={rangeState}
                  onProjectBarMouseDown={onProjectBarMouseDown}
                  onProjectBarTouchStart={onProjectBarTouchStart}
                  onAllocMouseDown={onAllocMouseDown}
                  onAllocTouchStart={onAllocTouchStart}
                  onRowMouseDown={onRowMouseDown}
                  onRowTouchStart={onRowTouchStart}
                  onOpenPanel={setOpenPanelProjectId}
                  onOpenDemandClick={setOpenDemandToAssign}
                  onAllocationContextMenu={openAllocationPopoverAt}
                  CELL_WIDTH={CELL_WIDTH}
                  dates={dates}
                  totalCanvasWidth={totalCanvasWidth}
                  toLeft={toLeft}
                  toWidth={toWidth}
                  gridLines={gridLines}
                  xToDate={xToDate}
                />
              )}
            </div>
          </div>
        )}
      </div>

      {/* Saving indicators */}
      {(isApplying || isAllocSaving) && (
        <div className="pointer-events-none absolute inset-0 z-50 flex items-center justify-center rounded-2xl bg-white/50 dark:bg-gray-950/50">
          <div className="app-surface px-5 py-3 text-sm font-medium text-gray-700 dark:text-gray-200">
            {isApplying ? "Applying shift…" : "Saving…"}
          </div>
        </div>
      )}

      {/* Drag preview tooltip */}
      {dragState.isDragging && dragState.daysDelta !== 0 && (
        <div
          ref={dragTooltipRef}
          className="fixed z-50 pointer-events-none"
          style={{ left: mousePosRef.current.x + 12, top: mousePosRef.current.y - 8 }}
        >
          <ShiftPreviewTooltip
            preview={
              shiftPreview ?? {
                valid: true,
                deltaCents: 0,
                wouldExceedBudget: false,
                budgetUtilizationAfter: 0,
                conflictCount: 0,
                errors: [],
                warnings: [],
              }
            }
            projectName={dragState.projectName ?? ""}
            newStartDate={dragState.currentStartDate ?? today}
            newEndDate={dragState.currentEndDate ?? today}
            isLoading={isPreviewLoading}
          />
        </div>
      )}

      {/* Alloc drag tooltip */}
      {allocDragState.isActive &&
        allocDragState.daysDelta !== 0 &&
        allocDragState.currentStartDate &&
        allocDragState.currentEndDate && (
          <div
            ref={allocTooltipRef}
            className="fixed z-40 bg-gray-800 text-white text-xs px-2.5 py-1.5 rounded-lg pointer-events-none shadow-lg space-y-0.5"
            style={{ left: mousePosRef.current.x + 14, top: mousePosRef.current.y - 36 }}
          >
            <div className="font-semibold">{allocDragState.projectName}</div>
            <div className="opacity-80">
              {formatDateShort(allocDragState.currentStartDate)}
              {" – "}
              {formatDateShort(allocDragState.currentEndDate)}
            </div>
          </div>
        )}

      {/* Range-select hint */}
      {rangeState.isSelecting && rangeState.startDate && rangeState.currentDate && (
        <div
          ref={rangeHintRef}
          className="fixed z-40 bg-brand-700 text-white text-xs px-2 py-1 rounded-lg pointer-events-none shadow"
          style={{ left: mousePosRef.current.x + 12, top: mousePosRef.current.y - 28 }}
        >
          {(() => {
            const end = rangeState.currentDate;
            const [s, e] =
              rangeState.startDate <= end
                ? [rangeState.startDate, end]
                : [end, rangeState.startDate];
            const days = Math.round((e.getTime() - s.getTime()) / 86400000) + 1;
            return `${days} day${days !== 1 ? "s" : ""}`;
          })()}
        </div>
      )}

      {/* Allocation popover */}
      {popover && (
        <AllocationPopover
          allocationId={popover.allocationId}
          projectId={popover.projectId}
          onClose={() => setPopover(null)}
          onOpenPanel={(pid) => {
            setPopover(null);
            setOpenPanelProjectId(pid);
          }}
          anchorX={popover.x}
          anchorY={popover.y}
        />
      )}

      {/* New allocation popover */}
      {newAllocPopover && (
        <NewAllocationPopover
          resourceId={newAllocPopover.resourceId}
          startDate={newAllocPopover.startDate}
          endDate={newAllocPopover.endDate}
          suggestedProjectId={newAllocPopover.suggestedProjectId}
          anchorX={newAllocPopover.anchorX}
          anchorY={newAllocPopover.anchorY}
          onClose={() => setNewAllocPopover(null)}
          onCreated={() => setNewAllocPopover(null)}
        />
      )}

      {/* Project side panel */}
      {openPanelProjectId && (
        <ProjectPanel projectId={openPanelProjectId} onClose={() => setOpenPanelProjectId(null)} />
      )}

      {/* Open-demand assignment modal */}
      {openDemandToAssign && (
        <FillOpenDemandModal
          allocation={openDemandToAssign}
          onClose={() => setOpenDemandToAssign(null)}
          onSuccess={() => setOpenDemandToAssign(null)}
        />
      )}
    </div>
  );
}
