"use client";

import { clsx } from "clsx";
import { memo, startTransition, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useVirtualizer } from "@tanstack/react-virtual";
import { useTimelineContext, type TimelineAssignmentEntry } from "./TimelineContext.js";
import { applyVisualOverrides, type TimelineVisualOverrides } from "./allocationVisualState.js";
import { ConflictOverlay } from "./ConflictOverlay.js";
import { computeSubLanes } from "./utils.js";
import {
  TimelineTooltip,
  type HeatmapHoverData,
  type VacationHoverData,
} from "./TimelineTooltip.js";
import { ROW_HEIGHT, SUB_LANE_HEIGHT, LABEL_WIDTH } from "./timelineConstants.js";
import type {
  DragState,
  AllocDragState,
  RangeState,
  ShiftPreviewData,
  MultiSelectState,
} from "~/hooks/useTimelineDrag.js";
import {
  buildVacationBlocksByResource,
  renderVacationBlocks,
  renderRangeOverlay,
  renderOverbookingBlink,
  type VacationBlockInfo,
} from "./renderHelpers.js";
import {
  cancelHoverFrame,
  scheduleVacationHoverUpdate,
  updateTooltipPosition,
} from "./timelineHover.js";
import { buildResourceHeatmapHover } from "./timelineHeatmap.js";
import { buildResourceCapacitySeries } from "./timelineCapacity.js";
import { isAllocationScheduledOnDate } from "./timelineAvailability.js";
import {
  renderAllocBlocksFromData,
  renderLoadGraph,
  renderHeatmapOverlay,
  renderDailyBars,
  type AllocBlockData,
  type AllocMouseDownInfo,
} from "./timelineResourceRender.js";

// ─── Props ──────────────────────────────────────────────────────────────────

interface TimelineResourcePanelProps {
  scrollContainerRef: React.RefObject<HTMLDivElement | null>;
  dragState: DragState;
  allocDragState: AllocDragState;
  rangeState: RangeState;
  shiftPreview: ShiftPreviewData | null;
  contextResourceIds: string[];
  onAllocMouseDown: (e: React.MouseEvent, info: AllocMouseDownInfo) => void;
  onAllocTouchStart: (e: React.TouchEvent, info: AllocMouseDownInfo) => void;
  onRowMouseDown: (e: React.MouseEvent, info: RowMouseDownInfo) => void;
  onRowTouchStart: (e: React.TouchEvent, info: RowMouseDownInfo) => void;
  onAllocationContextMenu: (
    info: { allocationId: string; projectId: string; contextDate?: Date },
    anchorX: number,
    anchorY: number,
  ) => void;
  multiSelectState: MultiSelectState;
  optimisticAllocations: TimelineVisualOverrides;
  suppressHoverInteractions: boolean;
  onInlineEdit?: (
    allocationId: string,
    initialValues: { startDate: Date; endDate: Date; hoursPerDay: number },
    barRect: DOMRect,
  ) => void;
  /** Horizontal virtualization: current scroll offset and visible container width */
  scrollLeft?: number;
  containerWidth?: number;
  // Layout from useTimelineLayout
  CELL_WIDTH: number;
  dates: Date[];
  totalCanvasWidth: number;
  toLeft: (date: Date) => number;
  toWidth: (start: Date, end: Date) => number;
  gridLines: React.ReactNode;
  xToDate: (clientX: number, rect: DOMRect) => Date;
}

export interface RowMouseDownInfo {
  resourceId: string;
  startDate: Date;
  suggestedProjectId?: string;
}

// ─── Component ──────────────────────────────────────────────────────────────

function TimelineResourcePanelInner({
  scrollContainerRef,
  dragState,
  allocDragState,
  rangeState,
  shiftPreview,
  contextResourceIds,
  onAllocMouseDown,
  onAllocTouchStart,
  onRowMouseDown,
  onRowTouchStart,
  onAllocationContextMenu,
  multiSelectState,
  optimisticAllocations,
  suppressHoverInteractions,
  onInlineEdit,
  scrollLeft = 0,
  containerWidth = 1200,
  CELL_WIDTH,
  dates,
  totalCanvasWidth,
  toLeft,
  toWidth,
  gridLines,
  xToDate,
}: TimelineResourcePanelProps) {
  const {
    resources,
    allocsByResource,
    vacationsByResource,
    filters,
    viewStart,
    viewEnd,
    displayMode,
    heatmapScheme,
    blinkOverbookedDays,
    activeFilterCount,
  } = useTimelineContext();

  // ─── Heatmap hover state ────────────────────────────────────────────────────
  const heatmapRafRef = useRef<number | null>(null);
  const lastHeatmapDayRef = useRef<number>(-1);
  const vacationHoverRafRef = useRef<number | null>(null);
  const hoveredVacationKeyRef = useRef<string | null>(null);
  const pendingHeatmapRef = useRef<{
    clientX: number;
    rect: DOMRect;
    allocs: TimelineAssignmentEntry[];
  } | null>(null);
  const heatmapTooltipRef = useRef<HTMLDivElement | null>(null);
  const vacationTooltipRef = useRef<HTMLDivElement | null>(null);
  const heatmapTooltipPosRef = useRef({ left: 0, top: 0 });
  const vacationTooltipPosRef = useRef({ left: 0, top: 0 });

  const [heatmapHover, setHeatmapHover] = useState<HeatmapHoverData | null>(null);
  const [vacationHover, setVacationHover] = useState<VacationHoverData | null>(null);

  // ─── Virtual row list ────────────────────────────────────────────────────────
  const rowVirtualizer = useVirtualizer({
    count: resources.length,
    getScrollElement: () => scrollContainerRef.current,
    estimateSize: () => ROW_HEIGHT,
    overscan: 5,
  });
  const virtualItems = rowVirtualizer.getVirtualItems();
  const totalRowHeight = rowVirtualizer.getTotalSize();

  const visualAllocsByResource = useMemo(() => {
    if (optimisticAllocations.size === 0) return allocsByResource;

    const next = new Map<string, TimelineAssignmentEntry[]>();
    for (const [resourceId, allocs] of allocsByResource) {
      next.set(resourceId, applyVisualOverrides(allocs, optimisticAllocations));
    }
    return next;
  }, [allocsByResource, optimisticAllocations]);

  const resourceCapacityById = useMemo(
    () => buildResourceCapacitySeries(visualAllocsByResource, vacationsByResource, dates),
    [dates, vacationsByResource, visualAllocsByResource],
  );

  // ─── Memo 1: resourceRows — which rows to render ─────────────────────────
  // (virtualizer handles which subset is visible; this memo just pre-computes
  //  per-row data that the render loop needs)
  const resourceRows = useMemo(() => {
    const contextSet = new Set(contextResourceIds);
    return resources.map((resource) => {
      const allocs = visualAllocsByResource.get(resource.id) ?? [];
      const isContextResource = contextSet.has(resource.id);
      return { resource, allocs, isContextResource };
    });
  }, [resources, visualAllocsByResource, contextResourceIds]);

  // ─── Memo 2: vacationBlocks — vacation bar positions per resource ─────────
  const vacationBlocksByResource = useMemo(
    () =>
      buildVacationBlocksByResource(
        vacationsByResource,
        filters.showVacations,
        toLeft,
        toWidth,
        CELL_WIDTH,
        totalCanvasWidth,
        filters.showWeekends,
      ),
    [
      vacationsByResource,
      toLeft,
      toWidth,
      CELL_WIDTH,
      totalCanvasWidth,
      filters.showVacations,
      filters.showWeekends,
    ],
  );

  // ─── Memo 3: assignmentBlocks — pre-computed per resource for strip mode ──
  // (Bar mode computes differently per-day, so we only pre-compute for strip.)
  const assignmentBlocksByResource = useMemo(() => {
    if (displayMode === "bar")
      return new Map<string, { laneCount: number; blockData: AllocBlockData[] }>();

    const result = new Map<string, { laneCount: number; blockData: AllocBlockData[] }>();
    for (const { resource, allocs } of resourceRows) {
      if (allocs.length === 0) continue;

      const subLaneMap = computeSubLanes(
        allocs.map((a) => ({
          id: a.id,
          startDate: new Date(a.startDate),
          endDate: new Date(a.endDate),
        })),
      );
      const laneCount = subLaneMap.size > 0 ? Math.max(...subLaneMap.values()) + 1 : 1;
      const blockData: AllocBlockData[] = allocs.map((alloc) => ({
        alloc,
        lane: subLaneMap.get(alloc.id) ?? 0,
      }));
      result.set(resource.id, { laneCount, blockData });
    }
    return result;
  }, [displayMode, resourceRows]);

  // ─── Memo 4: utilization per resource for row background tint ───────────
  const utilizationByResource = useMemo(() => {
    const result = new Map<string, number>(); // resourceId -> avg utilization pct
    for (const { resource, allocs } of resourceRows) {
      if (allocs.length === 0) continue;
      const capacity = resourceCapacityById.get(resource.id);
      let totalPct = 0;
      let dayCount = 0;
      for (let dayIndex = 0; dayIndex < dates.length; dayIndex++) {
        const date = dates[dayIndex];
        if (!date) continue;
        const bookingFactor = capacity?.bookingFactorsByDay[dayIndex] ?? 1;
        const capacityHours = capacity?.capacityHoursByDay[dayIndex] ?? 8;
        let dayH = 0;
        for (const a of allocs) {
          if (isAllocationScheduledOnDate(a, date)) {
            dayH += a.hoursPerDay * bookingFactor;
          }
        }
        if (dayH > 0 && capacityHours > 0) {
          totalPct += (dayH / capacityHours) * 100;
          dayCount++;
        }
      }
      if (dayCount > 0) {
        result.set(resource.id, totalPct / dayCount);
      }
    }
    return result;
  }, [dates, resourceCapacityById, resourceRows]);

  // ─── Heatmap row hover handler ────────────────────────────────────────────
  const handleRowHeatmapMove = useCallback(
    (e: React.MouseEvent, allocs: TimelineAssignmentEntry[]) => {
      updateTooltipPosition(heatmapTooltipPosRef, heatmapTooltipRef, e.clientX, e.clientY, 16, -52);
      const rect = e.currentTarget.getBoundingClientRect();
      const dayIndex = Math.floor((e.clientX - rect.left) / CELL_WIDTH);
      if (dayIndex === lastHeatmapDayRef.current) return;

      pendingHeatmapRef.current = { clientX: e.clientX, rect, allocs };
      if (heatmapRafRef.current !== null) return;

      heatmapRafRef.current = requestAnimationFrame(() => {
        heatmapRafRef.current = null;
        const pending = pendingHeatmapRef.current;
        pendingHeatmapRef.current = null;
        if (!pending) return;

        const { clientX, rect: r, allocs: a } = pending;
        const dayIdx = Math.floor((clientX - r.left) / CELL_WIDTH);
        const date = dates[dayIdx];
        if (!date) {
          lastHeatmapDayRef.current = -1;
          startTransition(() => setHeatmapHover(null));
          return;
        }
        lastHeatmapDayRef.current = dayIdx;

        const resourceId = a[0]?.resourceId ?? null;
        const capacity = resourceId ? resourceCapacityById.get(resourceId) : undefined;
        const nextHeatmap = buildResourceHeatmapHover(date, a, {
          ...(capacity?.capacityHoursByDay[dayIdx] !== undefined
            ? { capacityHours: capacity.capacityHoursByDay[dayIdx] }
            : {}),
          ...(capacity?.bookingFactorsByDay[dayIdx] !== undefined
            ? { bookingFactor: capacity.bookingFactorsByDay[dayIdx] }
            : {}),
        });
        startTransition(() => {
          setHeatmapHover(nextHeatmap);
        });
      });
    },
    [CELL_WIDTH, dates, resourceCapacityById],
  );

  // ─── Vacation hover ───────────────────────────────────────────────────────
  const handleRowVacationHover = useCallback(
    (e: React.MouseEvent, resourceId: string) => {
      updateTooltipPosition(
        vacationTooltipPosRef,
        vacationTooltipRef,
        e.clientX,
        e.clientY,
        14,
        -8,
      );
      scheduleVacationHoverUpdate({
        frameRef: vacationHoverRafRef,
        hoveredKeyRef: hoveredVacationKeyRef,
        resourceId,
        clientX: e.clientX,
        rect: e.currentTarget.getBoundingClientRect(),
        xToDate,
        vacations: vacationsByResource.get(resourceId) ?? [],
        onHoverChange: (hit) => {
          startTransition(() => {
            setVacationHover(hit);
          });
        },
      });
    },
    [vacationsByResource, xToDate],
  );

  const clearHoverTooltips = useCallback(() => {
    cancelHoverFrame(heatmapRafRef);
    cancelHoverFrame(vacationHoverRafRef);

    const shouldClearHeatmap = lastHeatmapDayRef.current !== -1;
    const shouldClearVacation = hoveredVacationKeyRef.current !== null;

    lastHeatmapDayRef.current = -1;
    hoveredVacationKeyRef.current = null;

    if (shouldClearHeatmap || shouldClearVacation) {
      startTransition(() => {
        if (shouldClearHeatmap) setHeatmapHover(null);
        if (shouldClearVacation) setVacationHover(null);
      });
    }
  }, []);

  // ─── Cleanup rAF on unmount ───────────────────────────────────────────────
  useEffect(
    () => () => {
      cancelHoverFrame(heatmapRafRef);
      cancelHoverFrame(vacationHoverRafRef);
    },
    [],
  );

  useEffect(() => {
    if (!suppressHoverInteractions) return;
    clearHoverTooltips();
  }, [clearHoverTooltips, suppressHoverInteractions]);

  // ─── Render helpers ───────────────────────────────────────────────────────

  if (resources.length === 0) {
    return (
      <div className="text-center py-16 text-gray-400">
        No allocations in this time range{activeFilterCount > 0 && " (filtered)"}.
      </div>
    );
  }

  return (
    <div
      style={{
        height: totalRowHeight,
        position: "relative",
      }}
    >
      {virtualItems.map((virtualRow) => {
        const rowData = resourceRows[virtualRow.index];
        if (!rowData) return null;
        const { resource, allocs, isContextResource } = rowData;
        const inBarMode = displayMode === "bar";
        const precomputed = assignmentBlocksByResource.get(resource.id);
        const laneCount = inBarMode ? 1 : (precomputed?.laneCount ?? 1);
        const rowHeight = inBarMode
          ? ROW_HEIGHT
          : Math.max(ROW_HEIGHT, laneCount * SUB_LANE_HEIGHT + 16);

        // Utilization background tint — only highlight over-utilization
        const utilPct = utilizationByResource.get(resource.id) ?? 0;
        const utilBg =
          utilPct > 100
            ? "rgba(254,202,202,0.18)" // red tint for over-utilized
            : undefined;

        return (
          <div
            key={resource.id}
            data-index={virtualRow.index}
            ref={rowVirtualizer.measureElement}
            style={{
              position: "absolute",
              top: 0,
              left: 0,
              width: "100%",
              transform: `translateY(${virtualRow.start}px)`,
            }}
          >
            <div
              className={clsx(
                "flex border-b border-gray-100 dark:border-gray-800 hover:bg-gray-50/40 dark:hover:bg-[rgb(var(--surface-elevated)/0.3)] group transition-colors",
                dragState.isDragging && isContextResource && "border-l-4 border-l-brand-400",
              )}
              style={{ height: rowHeight, ...(utilBg ? { backgroundColor: utilBg } : {}) }}
            >
              {/* Label column */}
              <div
                className={clsx(
                  "flex-shrink-0 border-r border-gray-200 dark:border-gray-700 flex items-center px-4 gap-2.5 bg-white dark:bg-[rgb(var(--surface-card))] sticky left-0 z-30 group-hover:bg-gray-50 dark:group-hover:bg-[rgb(var(--surface-elevated))]",
                  dragState.isDragging && isContextResource && "bg-brand-50 dark:bg-brand-950/40",
                )}
                style={{ width: LABEL_WIDTH }}
              >
                <div className="w-8 h-8 rounded-full bg-brand-100 dark:bg-brand-900/40 flex items-center justify-center text-xs font-bold text-brand-700 dark:text-brand-300 flex-shrink-0">
                  {resource.displayName.slice(0, 2).toUpperCase()}
                </div>
                <div className="min-w-0" data-resource-hover-id={resource.id}>
                  <div className="text-sm font-medium text-gray-900 dark:text-gray-100 truncate cursor-pointer hover:text-brand-600 dark:hover:text-brand-400 transition-colors">
                    {resource.displayName}
                  </div>
                  <div className="text-xs text-gray-400 dark:text-gray-500 truncate">
                    {resource.chapter ?? resource.eid}
                  </div>
                </div>
              </div>

              {/* Row canvas */}
              <div
                data-testid="timeline-resource-row-canvas"
                data-resource-id={resource.id}
                data-resource-eid={resource.eid}
                data-resource-name={resource.displayName}
                className="relative overflow-hidden touch-none"
                style={{ width: totalCanvasWidth, height: rowHeight, touchAction: "none" }}
                onMouseDown={(e) => {
                  const rect = e.currentTarget.getBoundingClientRect();
                  const date = xToDate(e.clientX, rect);
                  onRowMouseDown(e, { resourceId: resource.id, startDate: date });
                }}
                onTouchStart={(e) => {
                  const rect = e.currentTarget.getBoundingClientRect();
                  const date = xToDate(e.touches[0]?.clientX ?? 0, rect);
                  onRowTouchStart(e, { resourceId: resource.id, startDate: date });
                }}
                onMouseMove={(e) => {
                  if (suppressHoverInteractions) return;
                  handleRowHeatmapMove(e, allocs);
                  handleRowVacationHover(e, resource.id);
                }}
                onMouseLeave={clearHoverTooltips}
              >
                {gridLines}
                {inBarMode
                  ? renderDailyBars(
                      allocs,
                      rowHeight,
                      CELL_WIDTH,
                      dates,
                      allocDragState,
                      onAllocMouseDown,
                      onAllocTouchStart,
                      onAllocationContextMenu,
                      toLeft,
                      toWidth,
                      totalCanvasWidth,
                      resourceCapacityById.get(resource.id),
                      multiSelectState,
                      suppressHoverInteractions,
                    )
                  : renderAllocBlocksFromData(
                      precomputed?.blockData ?? [],
                      allocs,
                      dragState,
                      allocDragState,
                      toLeft,
                      toWidth,
                      CELL_WIDTH,
                      totalCanvasWidth,
                      onAllocMouseDown,
                      onAllocTouchStart,
                      onAllocationContextMenu,
                      multiSelectState,
                      suppressHoverInteractions,
                      onInlineEdit,
                      scrollLeft,
                      containerWidth,
                      optimisticAllocations.size > 0
                        ? new Set(optimisticAllocations.keys())
                        : undefined,
                    )}
                {filters.showVacations &&
                  renderVacationBlocks(vacationBlocksByResource.get(resource.id) ?? [], rowHeight)}
                {displayMode === "strip" &&
                  renderLoadGraph(allocs, dates, CELL_WIDTH, resourceCapacityById.get(resource.id))}
                {displayMode === "heatmap" &&
                  renderHeatmapOverlay(
                    allocs,
                    dates,
                    CELL_WIDTH,
                    heatmapScheme,
                    resourceCapacityById.get(resource.id),
                  )}
                {blinkOverbookedDays &&
                  renderOverbookingBlink(
                    allocs,
                    dates,
                    CELL_WIDTH,
                    resourceCapacityById.get(resource.id)?.capacityHoursByDay,
                    resourceCapacityById.get(resource.id)?.bookingFactorsByDay,
                  )}
                {renderRangeOverlay(
                  rangeState,
                  resource.id,
                  rowHeight,
                  toLeft,
                  toWidth,
                  CELL_WIDTH,
                )}

                {dragState.isDragging &&
                  dragState.projectId &&
                  shiftPreview &&
                  !shiftPreview.valid &&
                  shiftPreview.conflictCount > 0 &&
                  allocs.some((a) => a.projectId === dragState.projectId) && (
                    <ConflictOverlay
                      left={toLeft(dragState.currentStartDate ?? viewStart) + 2}
                      width={
                        toWidth(
                          dragState.currentStartDate ?? viewStart,
                          dragState.currentEndDate ?? viewEnd,
                        ) - 4
                      }
                      height={rowHeight - 8}
                      type="availability"
                      message={`${shiftPreview.conflictCount} conflict(s)`}
                    />
                  )}
              </div>
            </div>
          </div>
        );
      })}

      {/* Tooltips rendered inside the panel so they live near their data source */}
      <TimelineTooltip
        heatmapTooltipRef={heatmapTooltipRef}
        heatmapTooltipPos={heatmapTooltipPosRef.current}
        vacationTooltipRef={vacationTooltipRef}
        vacationTooltipPos={vacationTooltipPosRef.current}
        heatmapHover={heatmapHover}
        vacationHover={vacationHover}
      />
    </div>
  );
}

// ResourcePanelTooltips removed — now uses shared TimelineTooltip component

export const TimelineResourcePanel = memo(TimelineResourcePanelInner);

// ─── Re-export types for consumers ──────────────────────────────────────────
export type { AllocBlockData, AllocMouseDownInfo } from "./timelineResourceRender.js";
export type { VacationBlockInfo } from "./renderHelpers.js";
