import React, { startTransition, useState, useRef, useEffect, useLayoutEffect, useMemo, useCallback } from "react";
import { format, addHours, differenceInHours, startOfDay, addDays, subDays, startOfMonth, addMonths, parseISO } from "date-fns";
import { motion, useAnimationControls, useReducedMotion } from "framer-motion";
import {
  Maximize2, Minimize2,
  Stethoscope, Syringe, Pill, Calendar, Beaker, ImageIcon,
  ChevronDown,
  type LucideIcon
} from "lucide-react";
import { cn } from "../utils/helpers";
import { formatSpecificTime } from "../utils/eventTime";
import { useHealthStore } from '../store/useHealthStore';
import type { TimelineEvent, EventType, Medication, Appointment, LabResult, ImagingEvent } from "../types/medical";
import type { APIDiagnosis, APIPrescription } from "../services/medical";
import { Popover, PopoverTrigger, PopoverContent} from "./ui/popover";
import { EventPopoverContent, GroupedEventsPopoverContent } from "./EventPopoverContent";

// --- Constants ---
const HOUR_MS = 3_600_000;
const DAY_MS = 24 * HOUR_MS;

// Discrete zoom presets — replaces continuous pinch-zoom for a more
// scheduler-like UX. Switching a preset preserves the viewport center so
// the user doesn't lose their place. Week is the default.
const ZOOM_PRESETS = [
  { id: 'day',     label: 'Day',      hours: 24 },
  { id: 'week',    label: 'Week',     hours: 24 * 7 },
  { id: '2weeks',  label: '2 Weeks',  hours: 24 * 14 },
  { id: 'month',   label: 'Month',    hours: 24 * 30 },
  { id: '4months', label: '4 Months', hours: 24 * 120 },
] as const;
const DEFAULT_ZOOM_HOURS = 24 * 7;
const POINT_PACKING_BUFFER_MS = 0;
const MEDICATION_PACKING_BUFFER_MS = HOUR_MS * 12;

// Precomputed event node base class — joining once at module load avoids
// running cn() per event per render frame. CRITICAL: only `transition-colors`
// here, never `transition-all`. Position is driven by inline
// `transform: translate3d(...)` updated every pan/scroll frame; if `transform`
// is in the transition list the nodes animate between every frame and visibly
// wobble during pan and vertical scroll. Hover effects must stay color-only.
const BAR_BUTTON_BASE =
  "absolute z-10 overflow-visible transition-colors focus:outline-none";

type TimelineAnimatedSurfaceProps = {
  children: React.ReactNode;
  className?: string;
  displayRow: number;
  layoutBox?: {
    height: number;
    left: number;
    top: number;
    width: number;
  };
  layoutModeKey: string;
  rowHeight: number;
  style?: React.CSSProperties;
  visualSignature: string;
};

function TimelineAnimatedSurface({
  children,
  className,
  displayRow,
  layoutBox,
  layoutModeKey,
  rowHeight,
  style,
  visualSignature,
}: TimelineAnimatedSurfaceProps) {
  const prefersReducedMotion = useReducedMotion();
  const controls = useAnimationControls();
  const previousStateRef = useRef<{
    displayRow: number;
    layoutBox?: {
      height: number;
      left: number;
      top: number;
      width: number;
    };
    layoutModeKey: string;
    visualSignature: string;
  } | null>(null);

  useLayoutEffect(() => {
    const nextState = { displayRow, layoutBox, layoutModeKey, visualSignature };

    if (prefersReducedMotion) {
      previousStateRef.current = nextState;
      return;
    }

    const previousState = previousStateRef.current;
    const motionTransition = {
      x: { type: "spring", stiffness: 320, damping: 29, mass: 0.58 },
      y: { type: "spring", stiffness: 360, damping: 30, mass: 0.55 },
      scale: { duration: 0.18, ease: [0.22, 1, 0.36, 1] },
      scaleX: { duration: 0.18, ease: [0.22, 1, 0.36, 1] },
      scaleY: { duration: 0.18, ease: [0.22, 1, 0.36, 1] },
      opacity: { duration: 0.16, ease: "easeOut" },
    } as const;

    if (!previousState) {
      controls.set({ x: 0, y: 0, scale: 0.975, scaleX: 1, scaleY: 1, opacity: 0.9 });
      void controls.start({ x: 0, y: 0, scale: 1, scaleX: 1, scaleY: 1, opacity: 1, transition: motionTransition });
      previousStateRef.current = nextState;
      return;
    }

    const rowDelta = previousState.displayRow - displayRow;
    const movementHintY = Math.max(-14, Math.min(14, rowDelta * Math.min(14, rowHeight * 0.32)));
    const didVisualChange = previousState.visualSignature !== visualSignature;
    const didModeChange = previousState.layoutModeKey !== layoutModeKey;

    if (Math.abs(movementHintY) > 0.25 || didVisualChange || didModeChange) {
      const initialX = (() => {
        if (!didModeChange || !previousState.layoutBox || !layoutBox) return 0;
        const previousCenterX = previousState.layoutBox.left + previousState.layoutBox.width / 2;
        const nextCenterX = layoutBox.left + layoutBox.width / 2;
        return Math.max(-24, Math.min(24, previousCenterX - nextCenterX));
      })();
      const initialY = (() => {
        if (!didModeChange || !previousState.layoutBox || !layoutBox) return movementHintY;
        const previousCenterY = previousState.layoutBox.top + previousState.layoutBox.height / 2;
        const nextCenterY = layoutBox.top + layoutBox.height / 2;
        return Math.max(-18, Math.min(18, previousCenterY - nextCenterY + movementHintY));
      })();
      const initialScaleX = (() => {
        if (!didModeChange || !previousState.layoutBox || !layoutBox || layoutBox.width <= 0) {
          return didVisualChange ? 0.97 : 1;
        }
        const ratio = previousState.layoutBox.width / layoutBox.width;
        return Math.max(0.74, Math.min(1.18, ratio));
      })();
      const initialScaleY = (() => {
        if (!didModeChange || !previousState.layoutBox || !layoutBox || layoutBox.height <= 0) {
          return didVisualChange ? 0.985 : 1;
        }
        const ratio = previousState.layoutBox.height / layoutBox.height;
        return Math.max(0.82, Math.min(1.12, ratio));
      })();

      controls.set({
        x: initialX,
        y: initialY,
        scale: 1,
        scaleX: initialScaleX,
        scaleY: initialScaleY,
        opacity: didVisualChange || didModeChange ? 0.92 : 1,
      });
      void controls.start({ x: 0, y: 0, scale: 1, scaleX: 1, scaleY: 1, opacity: 1, transition: motionTransition });
    }

    previousStateRef.current = nextState;
  }, [controls, displayRow, layoutBox, layoutModeKey, prefersReducedMotion, rowHeight, visualSignature]);

  if (prefersReducedMotion) {
    return (
      <span className={className} style={style}>
        {children}
      </span>
    );
  }

  return (
    <motion.span
      animate={controls}
      className={cn("block will-change-transform", className)}
      initial={false}
      style={style}
    >
      {children}
    </motion.span>
  );
}

// Vitals are intentionally excluded from the Timeline — the EventType union in
// medical.ts still has 'vitals' (used by other parts of the app) but the
// Timeline never displays them.
export type DisplayedEventType = Exclude<EventType, 'vitals'>;
export type DisplayedTimelineEvent = Exclude<TimelineEvent, { type: 'vitals' }>;

export const ICON_MAP: Record<DisplayedEventType, LucideIcon> = {
  "appointments": Stethoscope,
  "labs": Beaker,
  "prescriptions": Pill,
  "vaccinations": Syringe,
  "imaging": ImageIcon,
};

export const COLOR_MAP: Record<DisplayedEventType, string> = {
  "appointments": "bg-blue-100 text-blue-600 border-blue-200",
  "labs": "bg-rose-100 text-rose-600 border-rose-200",
  "prescriptions": "bg-emerald-100 text-emerald-600 border-emerald-200",
  "vaccinations": "bg-purple-100 text-purple-600 border-purple-200",
  "imaging": "bg-sky-100 text-sky-600 border-sky-200",
};

const EVENT_DOT_CLASS_MAP: Record<DisplayedEventType, string> = {
  appointments: "bg-blue-500",
  labs: "bg-rose-500",
  prescriptions: "bg-emerald-500",
  vaccinations: "bg-purple-500",
  imaging: "bg-sky-500",
};

// Per-type styling for the timeline node marker, trailing connector, and
// capsule label. The circular marker gives the component a more literal
// timeline feel than a wall of rectangular bars.
const BAR_STYLE_MAP: Record<
  DisplayedEventType,
  { marker: string; connector: string; capsule: string; text: string }
> = {
  appointments: {
    marker: "border-blue-500 bg-white text-blue-600 shadow-[0_0_0_4px_rgba(219,234,254,0.9)]",
    connector: "bg-blue-200/80",
    capsule: "border-blue-200 bg-blue-50/95 hover:bg-blue-100/95",
    text: "text-blue-700",
  },
  labs: {
    marker: "border-rose-500 bg-white text-rose-600 shadow-[0_0_0_4px_rgba(255,228,230,0.9)]",
    connector: "bg-rose-200/80",
    capsule: "border-rose-200 bg-rose-50/95 hover:bg-rose-100/95",
    text: "text-rose-700",
  },
  prescriptions: {
    marker: "border-emerald-500 bg-white text-emerald-600 shadow-[0_0_0_4px_rgba(209,250,229,0.95)]",
    connector: "bg-emerald-200/80",
    capsule: "border-emerald-200 bg-emerald-50/95 hover:bg-emerald-100/95",
    text: "text-emerald-700",
  },
  vaccinations: {
    marker: "border-purple-500 bg-white text-purple-600 shadow-[0_0_0_4px_rgba(233,213,255,0.9)]",
    connector: "bg-purple-200/80",
    capsule: "border-purple-200 bg-purple-50/95 hover:bg-purple-100/95",
    text: "text-purple-700",
  },
  imaging: {
    marker: "border-sky-500 bg-white text-sky-600 shadow-[0_0_0_4px_rgba(224,242,254,0.9)]",
    connector: "bg-sky-200/80",
    capsule: "border-sky-200 bg-sky-50/95 hover:bg-sky-100/95",
    text: "text-sky-700",
  },
};

export function getTimelineSubtext(event: DisplayedTimelineEvent) {
  if (event.type === 'appointments') {
    const time = formatSpecificTime(event.time);
    return [time, event.doctor].filter(Boolean).join(' · ');
  }
  if (event.type === 'labs') return event.orderingPhysician || 'Lab result';
  if (event.type === 'vaccinations') return (event as import('../types/medical').VaccinationEvent).administrationPlace || 'Vaccination';
  if (event.type === 'imaging') return (event as ImagingEvent).hospital || 'Imaging';
  return `${event.dosage} · ${event.frequency}`;
}

export function getTimelineTitle(event: DisplayedTimelineEvent) {
  if (event.type === 'appointments') return event.reason;
  if (event.type === 'labs') return event.testName;
  if (event.type === 'vaccinations') return (event as import('../types/medical').VaccinationEvent).name;
  if (event.type === 'imaging') return (event as ImagingEvent).name;
  return event.name;
}

export function formatOptionalDate(date?: string) {
  if (!date) return '—';
  try {
    return format(parseISO(date), "MMM d, yyyy");
  } catch {
    return date;
  }
}

export function countDaysFrom(date?: string) {
  if (!date) return null;
  const parsed = parseISO(date);
  if (Number.isNaN(parsed.getTime())) return null;
  return Math.max(0, Math.ceil((Date.now() - parsed.getTime()) / DAY_MS));
}

export function countInclusiveDays(start?: string, end?: string) {
  if (!start || !end) return null;
  const parsedStart = parseISO(start);
  const parsedEnd = parseISO(end);
  if (Number.isNaN(parsedStart.getTime()) || Number.isNaN(parsedEnd.getTime())) return null;
  return Math.max(1, Math.round((parsedEnd.getTime() - parsedStart.getTime()) / DAY_MS) + 1);
}

export function isPrescriptionActive(prescription: Pick<APIPrescription, 'endDate'> | null, fallbackEndDate?: string) {
  const endDate = prescription?.endDate || fallbackEndDate;
  if (!endDate) return true;
  const parsedEnd = parseISO(endDate);
  if (Number.isNaN(parsedEnd.getTime())) return true;
  return parsedEnd.getTime() >= Date.now();
}

export function findLinkedDiagnosisForPrescription(
  prescription: APIPrescription | null,
  diagnoses: APIDiagnosis[]
) {
  if (!prescription) return null;

  if (prescription.diagnosisId) {
    const direct = diagnoses.find((diagnosis) => diagnosis._id === prescription.diagnosisId);
    if (direct) return direct;
  }

  return diagnoses.find((diagnosis) =>
    (diagnosis.relatedPrescriptions || []).some((relatedPrescription) => relatedPrescription._id === prescription._id)
  ) || null;
}

function resolveMedicationDetailMode(
  pixelsPerDay: number,
  previous: 'full' | 'label'
) {
  if (previous === 'full') return pixelsPerDay < 64 ? 'label' : 'full';
  return pixelsPerDay >= 80 ? 'full' : 'label';
}

function resolvePointVisualMode(
  pixelsPerDay: number,
  previous: 'timed' | 'day' | 'dot'
) {
  if (previous === 'timed') return pixelsPerDay < 1080 ? 'day' : 'timed';
  if (previous === 'day') {
    if (pixelsPerDay >= 1320) return 'timed';
    if (pixelsPerDay < 72) return 'dot';
    return 'day';
  }
  return pixelsPerDay >= 96 ? 'day' : 'dot';
}

function parseTimeToMinutes(time: string | undefined, fallbackMinutes: number) {
  if (!time || time === '00:00') return fallbackMinutes;
  const [hours, minutes] = time.split(':').map(Number);
  if (Number.isNaN(hours) || Number.isNaN(minutes)) return fallbackMinutes;
  return Math.min(23 * 60 + 59, Math.max(0, hours * 60 + minutes));
}

function getTimedEventStartMs(event: Appointment | LabResult | import('../types/medical').VaccinationEvent | ImagingEvent) {
  const date = parseISO(event.date);
  const minutes = (event.type === 'appointments' || event.type === 'vaccinations')
    ? parseTimeToMinutes(event.time, 9 * 60)
    : event.type === 'imaging'
      ? parseTimeToMinutes(event.time, 13 * 60)
      : parseTimeToMinutes(event.time, 11 * 60);
  date.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0);
  return date.getTime();
}

interface TimelineProps {
  size?: "default" | "large";
  /**
   * When true the Timeline takes 100% of its parent's height (instead of
   * computing a fixed pixel height from MIN_LARGE_HEIGHT). Row height is
   * derived from the measured events area: targets `available / max(4, rows)`
   * so a typical layout shows 4 generously-sized rows with extra rows shrinking
   * gracefully. Used by the full-bleed TimelinePage layout; the dashboard
   * widget version leaves this unset and keeps its content-fit sizing.
   */
  fill?: boolean;
  initialMinimized?: boolean;
  isMinimized?: boolean;
  onToggleMinimize?: () => void;
  focusDate?: string;
}

export function Timeline({
  size = "default",
  fill = false,
  initialMinimized = false,
  isMinimized: propIsMinimized,
  onToggleMinimize: propOnToggleMinimize,
  focusDate
}: TimelineProps) {
  const [currentSize, setCurrentSize] = useState(size);
  const [internalMinimized, setInternalMinimized] = useState(initialMinimized);

  const isMinimized = propIsMinimized !== undefined ? propIsMinimized : internalMinimized;
  const toggleMinimize = propOnToggleMinimize || (() => setInternalMinimized(!internalMinimized));

  useEffect(() => {
    setCurrentSize(size);
  }, [size]);

  const { appointments, medications, labResults, vaccinations, imaging } = useHealthStore();

  const events = useMemo<TimelineEvent[]>(() => {
    return [...appointments, ...medications, ...labResults, ...(vaccinations || []), ...(imaging || [])];
  }, [appointments, medications, labResults, vaccinations, imaging]);

  // Global Range: dynamically expands as user scrolls
  const [globalStart, setGlobalStart] = useState<Date>(() => subDays(startOfDay(new Date()), 180));
  const [globalEnd, setGlobalEnd] = useState<Date>(() => addDays(startOfDay(new Date()), 180));

  // Initial viewport: Week preset centered on the current moment.
  const initialNow = useRef(new Date()).current;
  const [viewportStart, setViewportStart] = useState(addHours(initialNow, -DEFAULT_ZOOM_HOURS / 2));
  const [viewportDuration, setViewportDuration] = useState<number>(DEFAULT_ZOOM_HOURS);
  const appliedFocusDateRef = useRef<string | null>(null);

  useEffect(() => {
    let newStart = globalStart;
    let newEnd = globalEnd;
    let changed = false;

    const daysToStart = (viewportStart.getTime() - globalStart.getTime()) / DAY_MS;
    if (daysToStart < 30) {
      newStart = subDays(globalStart, 180);
      changed = true;
    }

    const viewportEndMs = viewportStart.getTime() + viewportDuration * HOUR_MS;
    const daysToEnd = (globalEnd.getTime() - viewportEndMs) / DAY_MS;
    if (daysToEnd < 30) {
      newEnd = addDays(globalEnd, 180);
      changed = true;
    }

    if (changed) {
      setGlobalStart(newStart);
      setGlobalEnd(newEnd);
    }
  }, [viewportStart, viewportDuration, globalStart, globalEnd]);

  const [activeFilters, setActiveFilters] = useState<DisplayedEventType[]>([]);
  const containerRef = useRef<HTMLDivElement>(null);
  const [containerWidth, setContainerWidth] = useState(0);
  // Only used in fill mode — measured from the events area's flex-allocated height.
  const [containerHeight, setContainerHeight] = useState(0);
  const containerLeftRef = useRef(0);

  // Read the real container size synchronously before paint so the first
  // render with the wrong dimensions never reaches the screen (otherwise the
  // timeline visibly snaps from default → real size on mount). In fill mode
  // we also need the height because row sizing is derived from it.
  useLayoutEffect(() => {
    if (containerRef.current) {
      const rect = containerRef.current.getBoundingClientRect();
      if (rect.width > 0) setContainerWidth(rect.width);
      if (fill && rect.height > 0) setContainerHeight(rect.height);
      containerLeftRef.current = rect.left;
    }
  }, [fill]);

  useEffect(() => {
    if (containerRef.current) {
      const resizeObserver = new ResizeObserver((entries) => {
        const { width, height } = entries[0].contentRect;
        // Ignore 0-size reports (e.g. when an ancestor toggles display:none
        // for keep-alive routing) so we don't reset the viewport state.
        if (width > 0) setContainerWidth(width);
        if (fill && height > 0) setContainerHeight(height);
        containerLeftRef.current = containerRef.current?.getBoundingClientRect().left ?? containerLeftRef.current;
      });
      resizeObserver.observe(containerRef.current);
      return () => resizeObserver.disconnect();
    }
  }, [fill]);

  useEffect(() => {
    const updateContainerLeft = () => {
      if (!containerRef.current) return;
      containerLeftRef.current = containerRef.current.getBoundingClientRect().left;
    };

    updateContainerLeft();
    window.addEventListener('resize', updateContainerLeft, { passive: true });
    window.addEventListener('scroll', updateContainerLeft, { passive: true, capture: true });
    return () => {
      window.removeEventListener('resize', updateContainerLeft);
      window.removeEventListener('scroll', updateContainerLeft, true);
    };
  }, []);

  const zoom = containerWidth / viewportDuration; // pixels per hour

  // Filter Events. Vitals are never displayed on the Timeline.
  const filteredEvents = useMemo(() => {
    const now = Date.now();
    const safeEvents = events || [];
    const filteredByTime = safeEvents.filter(e => {
      if (e.type === 'vitals') return false;
      if (new Date(e.date).getTime() > now) {
        return e.type === 'appointments' || e.type === 'prescriptions' || e.type === 'vaccinations' || e.type === 'imaging';
      }
      return true;
    });
    if (activeFilters.length === 0) return filteredByTime;
    return filteredByTime.filter(e => (activeFilters as EventType[]).includes(e.type));
  }, [events, activeFilters]);

  // Size-dependent primitives — pulled out of an object literal so they're
  // stable JS numbers/strings instead of a fresh object every render.
  const isLarge = currentSize === "large";
  const topOffset = isLarge ? 36 : 22;
  const labelTextClass = isLarge ? "text-sm" : "text-[10px]";

  // Precompute everything that doesn't depend on viewport: per-event start/end
  // in ms, labels, supporting metadata, and the joined classNames. This memo
  // only re-runs when filtered events change — pan/zoom never touches it.
  type LayoutedEvent = {
    id: string;
    event: TimelineEvent;
    row: number;
    dayStartMs: number;
    dayEndMs: number;
    startMs: number;
    endMs: number;
    durationHours: number;
    label: string;
    subtext: string;
    textColor: string;
    buttonClass: string;     // base positioning / pointer classes
    markerClass: string;
    connectorClass: string;
    capsuleClass: string;
  };

  const layoutedEvents = useMemo<LayoutedEvent[]>(() => {
    const packLanes = <T extends { packStartMs: number; packEndMs: number }>(items: T[]) => {
      const lanes: number[] = [];
      const sorted = items.slice().sort((a, b) => a.packStartMs - b.packStartMs);
      const out = new Array<{ item: T; lane: number }>(sorted.length);

      for (let i = 0; i < sorted.length; i++) {
        const item = sorted[i];
        let lane = -1;
        for (let j = 0; j < lanes.length; j++) {
          if (lanes[j] <= item.packStartMs) {
            lane = j;
            break;
          }
        }

        if (lane === -1) {
          lane = lanes.length;
          lanes.push(item.packEndMs);
        } else {
          lanes[lane] = item.packEndMs;
        }

        out[i] = { item, lane };
      }

      return out;
    };

    // Helper: snap a date string to local-day midnight ms (mutates the Date instance)
    const snapToDayMs = (raw: string | Date) => {
      const d = new Date(raw);
      d.setHours(0, 0, 0, 0);
      return d.getTime();
    };

    // Single unified pool — all types share the same rows, so a lab, an
    // appointment and a medication that don't overlap in time end up on the
    // same row. Only events that actually collide get pushed down a lane.
    type Pre = {
      event: TimelineEvent;
      dayStartMs: number;
      dayEndMs: number;
      startMs: number;
      endMs: number;
      packStartMs: number;
      packEndMs: number;
    };
    const pool: Pre[] = [];

    for (let i = 0; i < filteredEvents.length; i++) {
      const e = filteredEvents[i];
      if (e.type === 'prescriptions') {
        const med = e as Medication;
        const dayStartMs = snapToDayMs(med.startDate || med.date);
        const dayEndMs = snapToDayMs(med.endDate || med.date) + DAY_MS;
        const startMs = dayStartMs;
        let endMs = dayEndMs;
        if (endMs < startMs + DAY_MS) endMs = startMs + DAY_MS;
        pool.push({
          event: e,
          startMs,
          endMs,
          dayStartMs,
          dayEndMs,
          packStartMs: startMs,
          packEndMs: endMs + MEDICATION_PACKING_BUFFER_MS,
        });
      } else if (e.type === 'labs' || e.type === 'appointments' || e.type === 'vaccinations' || e.type === 'imaging') {
        const dayStartMs = snapToDayMs(e.date);
        const startMs = getTimedEventStartMs(e as Appointment | LabResult | import('../types/medical').VaccinationEvent | ImagingEvent);
        const durationMs = e.type === 'appointments' ? HOUR_MS : 45 * 60 * 1000;
        pool.push({
          event: e,
          dayStartMs,
          dayEndMs: dayStartMs + DAY_MS,
          startMs,
          endMs: startMs + durationMs,
          packStartMs: dayStartMs,
          packEndMs: dayStartMs + DAY_MS + POINT_PACKING_BUFFER_MS / 4,
        });
      }
      // vitals are filtered out upstream in filteredEvents
    }

    return packLanes(pool).map(({ item, lane }) => {
      const ev = item.event;
      const styles = BAR_STYLE_MAP[ev.type as DisplayedEventType];
      let label = '';
      if (ev.type === 'appointments') label = (ev as Appointment).reason;
      else if (ev.type === 'labs') label = (ev as LabResult).testName;
      else if (ev.type === 'vaccinations') label = (ev as import('../types/medical').VaccinationEvent).name;
      else if (ev.type === 'imaging') label = (ev as ImagingEvent).name;
      else if (ev.type === 'prescriptions') label = (ev as Medication).name;
      const subtext = getTimelineSubtext(ev as DisplayedTimelineEvent);
      // Test events injected by the dev panel use IDs prefixed "test-" — mark
      // them with a dashed border so they're visually distinguishable from
      // real data while still exercising the same layout/render path.
      const isTest = ev.id.startsWith('test-');
      return {
        id: ev.id,
        event: ev,
        row: lane,
        dayStartMs: item.dayStartMs,
        dayEndMs: item.dayEndMs,
        startMs: item.startMs,
        endMs: item.endMs,
        durationHours: (item.endMs - item.startMs) / HOUR_MS,
        label,
        subtext,
        textColor: styles.text,
        buttonClass: BAR_BUTTON_BASE,
        markerClass: "absolute left-0 rounded-full border-2 transition-colors " + styles.marker + (isTest ? " border-dashed" : ""),
        connectorClass: "absolute rounded-full transition-colors " + styles.connector,
        capsuleClass: "absolute rounded-full border shadow-sm transition-colors " + styles.capsule + (isTest ? " border-dashed" : ""),
      };
    });
  }, [filteredEvents]);

  // Numeric viewport start in ms — reused by tick/event/today line math.
  // Visual mode switches use a small hysteresis buffer so tiny zoom changes
  // around a threshold do not flip nodes between representations every frame.
  const viewportStartMs = viewportStart.getTime();
  const pixelsPerDay = zoom * 24;
  const zoomModesRef = useRef<{
    medicationDetailMode: 'full' | 'label';
    pointVisualMode: 'timed' | 'day' | 'dot';
  } | null>(null);
  if (!zoomModesRef.current) {
    zoomModesRef.current = {
      medicationDetailMode: pixelsPerDay >= 72 ? 'full' : 'label',
      pointVisualMode: pixelsPerDay >= 1200 ? 'timed' : pixelsPerDay >= 84 ? 'day' : 'dot',
    };
  }
  zoomModesRef.current.medicationDetailMode = resolveMedicationDetailMode(
    pixelsPerDay,
    zoomModesRef.current.medicationDetailMode
  );
  zoomModesRef.current.pointVisualMode = resolvePointVisualMode(
    pixelsPerDay,
    zoomModesRef.current.pointVisualMode
  );
  const medicationDetailMode = zoomModesRef.current.medicationDetailMode;
  const pointVisualMode = zoomModesRef.current.pointVisualMode;
  const totalBaseRows = useMemo(
    () => (layoutedEvents.length > 0 ? Math.max(...layoutedEvents.map((item) => item.row)) + 1 : 1),
    [layoutedEvents]
  );
  // Keep day cards and compact dots inside the same centered lane envelope so
  // zooming between those modes does not remap every item vertically.
  const totalDisplayRows = pointVisualMode === 'timed'
    ? totalBaseRows
    : Math.max(totalBaseRows, fill || isLarge ? 8 : 6);
  const medicationCardWidth = medicationDetailMode === 'full'
    ? Math.max(156, Math.min(isLarge ? 224 : 196, pixelsPerDay * 4.2))
    : Math.max(132, Math.min(isLarge ? 176 : 156, pixelsPerDay * 3.6));
  const targetEventNodeHeight = isLarge ? 52 : 46;
  const rowGap = 4;

  // Dynamic row sizing:
  //   • fill   → fit the available height until rows would squeeze below the
  //              fixed node height, then lock rows at the minimum lane size
  //              and allow vertical scrolling.
  //   • large  → 500px minimum height with centered rows.
  //   • default→ content-fit rows at the compact lane size.
  const bottomPadding = 12;
  let rowHeight: number;
  let medBarHeight: number;
  let finalHeight: number;
  let needsVerticalScroll = false;
  let scrollContentHeight = 0;
  let eventsOffsetY = 0;
  if (fill) {
    const MIN_FILL_ROW_HEIGHT = targetEventNodeHeight + rowGap;
    const MAX_FILL_ROW_HEIGHT = targetEventNodeHeight + 8;
    const measuredHeight = containerHeight > 0 ? containerHeight : 500;
    const available = Math.max(0, measuredHeight - topOffset - bottomPadding);
    const targetRows = Math.max(1, totalDisplayRows);
    const idealRowHeight = targetRows > 0 ? available / targetRows : MIN_FILL_ROW_HEIGHT;
    if (idealRowHeight < MIN_FILL_ROW_HEIGHT) {
      rowHeight = MIN_FILL_ROW_HEIGHT;
      needsVerticalScroll = true;
      scrollContentHeight = totalDisplayRows * rowHeight + bottomPadding;
    } else {
      rowHeight = Math.min(MAX_FILL_ROW_HEIGHT, idealRowHeight);
      eventsOffsetY = Math.max(0, (available - totalDisplayRows * rowHeight) / 2);
    }
    medBarHeight = Math.max(0, rowHeight - rowGap);
    finalHeight = measuredHeight;
  } else if (isLarge) {
    const MIN_LARGE_HEIGHT = 500;
    rowHeight = targetEventNodeHeight + rowGap;
    const contentHeight = totalDisplayRows * rowHeight;
    finalHeight = Math.max(topOffset + contentHeight + bottomPadding, MIN_LARGE_HEIGHT);
    const available = finalHeight - topOffset - bottomPadding;
    eventsOffsetY = Math.max(0, (available - contentHeight) / 2);
    medBarHeight = Math.max(0, rowHeight - rowGap);
  } else {
    rowHeight = targetEventNodeHeight + rowGap;
    medBarHeight = targetEventNodeHeight;
    finalHeight = topOffset + totalDisplayRows * rowHeight + bottomPadding;
  }
  const eventNodeHeight = Math.min(medBarHeight, targetEventNodeHeight);
  const eventVerticalPadding = Math.max(0, (rowHeight - eventNodeHeight) / 2);
  const pointDotSize = eventNodeHeight;
  const pointDotCollisionWidth = Math.max(28, Math.round(eventNodeHeight * 0.72));
  const pointGroupSize = eventNodeHeight;
  const pointGroupCollisionWidth = pointDotCollisionWidth;
  const compactLayoutPixelsPerDay = pointVisualMode === 'dot'
    ? Math.max(6, Math.round(pixelsPerDay / 6) * 6)
    : pixelsPerDay;
  const compactLayoutZoom = compactLayoutPixelsPerDay / 24;
  const pointDotCollisionSpanMs = compactLayoutZoom > 0
    ? (pointDotCollisionWidth / compactLayoutZoom) * HOUR_MS
    : Number.POSITIVE_INFINITY;
  const pointGroupCollisionSpanMs = compactLayoutZoom > 0
    ? (pointGroupCollisionWidth / compactLayoutZoom) * HOUR_MS
    : Number.POSITIVE_INFINITY;
  const centeredDisplayRows = useMemo(() => {
    const visualCenterRow = (totalDisplayRows - 1) / 2;
    return Array.from({ length: totalDisplayRows }, (_, row) => row)
      .sort(
        (leftRow, rightRow) =>
          Math.abs(leftRow - visualCenterRow) - Math.abs(rightRow - visualCenterRow) ||
          leftRow - rightRow
      )
      .slice(0, totalBaseRows);
  }, [totalBaseRows, totalDisplayRows]);

  const { renderedItems, totalRows } = useMemo(() => {
    type RawRenderedItem =
      | {
          id: string;
          kind: 'medication';
          item: LayoutedEvent;
          motionKey: string;
          row: number;
          displayRow: number;
          renderStartMs: number;
          renderEndMs: number;
          anchorMs: number;
        }
      | {
          id: string;
          kind: 'point';
          item: LayoutedEvent;
          motionKey: string;
          row: number;
          displayRow: number;
          pointMode: 'dot' | 'day' | 'timed';
          renderStartMs: number;
          renderEndMs: number;
          anchorMs: number;
        }
      | {
          id: string;
          kind: 'group';
          items: LayoutedEvent[];
          motionKey: string;
          row: number;
          displayRow: number;
          anchorMs: number;
          sizePx: number;
        };

    type OccupiedInterval = {
      kind: 'medication' | 'point' | 'group';
      leftMs: number;
      rightMs: number;
      rawIndex?: number;
    };

    type CompactPoint = {
      item: LayoutedEvent;
      anchorMs: number;
      leftMs: number;
      rightMs: number;
    };

    const rawItems: RawRenderedItem[] = [];
    const pointItems = layoutedEvents.filter((item) => item.event.type !== 'prescriptions');
    const medicationItems = layoutedEvents.filter((item) => item.event.type === 'prescriptions');
    const occupiedByRow = Array.from({ length: totalDisplayRows }, () => [] as OccupiedInterval[]);

    const overlapsInterval = (leftMs: number, rightMs: number, interval: OccupiedInterval) =>
      leftMs < interval.rightMs && rightMs > interval.leftMs;
    const getPointDayAnchorMs = (item: LayoutedEvent) => item.dayStartMs + DAY_MS / 2;
    const getCenteredBoundsMs = (centerMs: number, spanMs: number) => ({
      leftMs: centerMs - spanMs / 2,
      rightMs: centerMs + spanMs / 2,
    });

    const getDisplayRow = (row: number) => centeredDisplayRows[row] ?? row;
    const visualCenterRow = (totalDisplayRows - 1) / 2;
    const getPreferredRows = (baseRow: number) => {
      const baseDisplayRow = getDisplayRow(baseRow);
      const adjacentRows = [baseDisplayRow - 1, baseDisplayRow + 1]
        .filter((row) => row >= 0 && row < totalDisplayRows)
        .sort(
          (leftRow, rightRow) =>
            Math.abs(leftRow - visualCenterRow) - Math.abs(rightRow - visualCenterRow) ||
            leftRow - rightRow
        );
      return [baseDisplayRow, ...adjacentRows];
    };
    const canMergeCompactItem = (
      existing: Extract<RawRenderedItem, { kind: 'point' | 'group' }>,
      compactPoint: CompactPoint
    ) => {
      const memberAnchorMs = existing.kind === 'group'
        ? existing.items.map((item) => getPointDayAnchorMs(item))
        : [existing.anchorMs];
      const nextAnchorMs = compactPoint.anchorMs;
      const minAnchorMs = Math.min(nextAnchorMs, ...memberAnchorMs);
      const maxAnchorMs = Math.max(nextAnchorMs, ...memberAnchorMs);
      return maxAnchorMs - minAnchorMs <= pointGroupCollisionSpanMs;
    };

    for (const medItem of medicationItems) {
      const medDisplayRow = getDisplayRow(medItem.row);
      occupiedByRow[medDisplayRow]?.push({
        kind: 'medication',
        leftMs: medItem.startMs,
        rightMs: medItem.endMs,
      });
    }

    if (pointVisualMode === 'dot') {
      const sortedCompactPoints: CompactPoint[] = pointItems
        .slice()
        .map((item) => {
          const anchorMs = getPointDayAnchorMs(item);
          const { leftMs, rightMs } = getCenteredBoundsMs(anchorMs, pointDotCollisionSpanMs);
          return {
            item,
            anchorMs,
            leftMs,
            rightMs,
          };
        })
        .sort((a, b) =>
          a.anchorMs - b.anchorMs ||
          a.item.startMs - b.item.startMs ||
          a.item.row - b.item.row ||
          a.item.id.localeCompare(b.item.id)
        );

      for (const compactPoint of sortedCompactPoints) {
        const baseDisplayRow = getDisplayRow(compactPoint.item.row);
        const preferredRows = getPreferredRows(compactPoint.item.row);
        const freeRow = preferredRows.find((row) =>
          occupiedByRow[row].every((interval) => !overlapsInterval(compactPoint.leftMs, compactPoint.rightMs, interval))
        );

        if (freeRow !== undefined) {
          const rawIndex = rawItems.push({
            id: compactPoint.item.id,
            kind: 'point',
            item: compactPoint.item,
            motionKey: compactPoint.item.id,
            row: freeRow,
            displayRow: freeRow,
            pointMode: 'dot',
            renderStartMs: compactPoint.anchorMs,
            renderEndMs: compactPoint.anchorMs,
            anchorMs: compactPoint.anchorMs,
          }) - 1;

          occupiedByRow[freeRow].push({
            kind: 'point',
            leftMs: compactPoint.leftMs,
            rightMs: compactPoint.rightMs,
            rawIndex,
          });
          continue;
        }

        const mergeTarget = preferredRows
          .flatMap((row) =>
            occupiedByRow[row]
              .filter((interval) => interval.kind !== 'medication' && interval.rawIndex !== undefined && overlapsInterval(compactPoint.leftMs, compactPoint.rightMs, interval))
              .map((interval) => ({
                row,
                interval,
                rawItem: rawItems[interval.rawIndex!] as RawRenderedItem,
                rowDistance: Math.abs(row - baseDisplayRow),
                centerDistance: Math.abs((interval.leftMs + interval.rightMs) / 2 - compactPoint.anchorMs),
              }))
              .filter(({ rawItem }) => (rawItem.kind === 'point' || rawItem.kind === 'group') && canMergeCompactItem(rawItem, compactPoint))
          )
          .sort((a, b) => a.rowDistance - b.rowDistance || a.centerDistance - b.centerDistance)[0];

        if (mergeTarget?.interval.rawIndex !== undefined) {
          const existing = rawItems[mergeTarget.interval.rawIndex];
          if (existing.kind === 'group') {
            const mergedItems = [...existing.items, compactPoint.item].sort((a, b) => a.startMs - b.startMs);
            const minAnchorMs = Math.min(...mergedItems.map((item) => getPointDayAnchorMs(item)));
            const maxAnchorMs = Math.max(...mergedItems.map((item) => getPointDayAnchorMs(item)));
            const groupAnchorMs = (minAnchorMs + maxAnchorMs) / 2;
            const groupBounds = getCenteredBoundsMs(groupAnchorMs, pointGroupCollisionSpanMs);

            rawItems[mergeTarget.interval.rawIndex] = {
              ...existing,
              id: `group-${mergedItems.map((item) => item.id).join('-')}`,
              items: mergedItems,
              anchorMs: groupAnchorMs,
              sizePx: pointGroupSize,
            };
            mergeTarget.interval.leftMs = groupBounds.leftMs;
            mergeTarget.interval.rightMs = groupBounds.rightMs;
          } else if (existing.kind === 'point') {
            const groupedItems = [existing.item, compactPoint.item].sort((a, b) => a.startMs - b.startMs);
            const minAnchorMs = Math.min(...groupedItems.map((item) => getPointDayAnchorMs(item)));
            const maxAnchorMs = Math.max(...groupedItems.map((item) => getPointDayAnchorMs(item)));
            const groupAnchorMs = (minAnchorMs + maxAnchorMs) / 2;
            const groupBounds = getCenteredBoundsMs(groupAnchorMs, pointGroupCollisionSpanMs);

            rawItems[mergeTarget.interval.rawIndex] = {
              id: `group-${groupedItems.map((item) => item.id).join('-')}`,
              kind: 'group',
              items: groupedItems,
              motionKey: existing.motionKey,
              row: existing.row,
              displayRow: existing.displayRow,
              anchorMs: groupAnchorMs,
              sizePx: pointGroupSize,
            };
            mergeTarget.interval.leftMs = groupBounds.leftMs;
            mergeTarget.interval.rightMs = groupBounds.rightMs;
          }
          continue;
        }

        const fallbackRow = preferredRows[0] ?? 0;
        const rawIndex = rawItems.push({
          id: compactPoint.item.id,
          kind: 'point',
          item: compactPoint.item,
          motionKey: compactPoint.item.id,
          row: fallbackRow,
          displayRow: fallbackRow,
          pointMode: 'dot',
          renderStartMs: compactPoint.anchorMs,
          renderEndMs: compactPoint.anchorMs,
          anchorMs: compactPoint.anchorMs,
        }) - 1;

        occupiedByRow[fallbackRow]?.push({
          kind: 'point',
          leftMs: compactPoint.leftMs,
          rightMs: compactPoint.rightMs,
          rawIndex,
        });
      }
    } else {
      for (const pointItem of pointItems) {
        const anchorMs = pointVisualMode === 'day'
          ? pointItem.dayStartMs + DAY_MS / 2
          : pointItem.startMs + (pointItem.endMs - pointItem.startMs) / 2;

        rawItems.push({
          id: pointItem.id,
          kind: 'point',
          item: pointItem,
          motionKey: pointItem.id,
          row: pointItem.row,
          displayRow: getDisplayRow(pointItem.row),
          pointMode: pointVisualMode,
          renderStartMs: pointVisualMode === 'day' ? pointItem.dayStartMs : pointItem.startMs,
          renderEndMs: pointVisualMode === 'day' ? pointItem.dayEndMs : pointItem.endMs,
          anchorMs,
        });
      }
    }

    for (const medItem of medicationItems) {
      rawItems.push({
        id: medItem.id,
        kind: 'medication',
        item: medItem,
        motionKey: medItem.id,
        row: medItem.row,
        displayRow: getDisplayRow(medItem.row),
        renderStartMs: medItem.startMs,
        renderEndMs: medItem.endMs,
        anchorMs: medItem.startMs + (medItem.endMs - medItem.startMs) / 2,
      });
    }

    return {
      renderedItems: rawItems,
      totalRows: totalDisplayRows,
    };
  }, [
    layoutedEvents,
    totalDisplayRows,
    pointDotCollisionWidth,
    pointDotSize,
    pointGroupCollisionWidth,
    pointGroupSize,
    pointVisualMode,
    centeredDisplayRows,
    pointDotCollisionSpanMs,
    pointGroupCollisionSpanMs,
  ]);
  // In fill mode the events area uses flex-1 instead of an explicit pixel
  // height, so heightStyle is empty. Minimize toggle still collapses to 0px.
  const heightStyle = fill
    ? (isMinimized ? { height: '0px' } : {})
    : { height: isMinimized ? '0px' : `${finalHeight}px` };

  // --- RAF-throttled state updates ---
  const rafRef = useRef<number | null>(null);
  const pendingUpdate = useRef<{ start?: Date; duration?: number } | null>(null);

  const flushUpdate = useCallback(() => {
    rafRef.current = null;
    if (pendingUpdate.current) {
      const { start, duration } = pendingUpdate.current;
      pendingUpdate.current = null;
      startTransition(() => {
        if (start !== undefined) setViewportStart(start);
        if (duration !== undefined) setViewportDuration(duration);
      });
    }
  }, []);

  const scheduleUpdate = useCallback((start?: Date, duration?: number) => {
    // Merge with pending
    if (!pendingUpdate.current) {
      pendingUpdate.current = {};
    }
    if (start !== undefined) pendingUpdate.current.start = start;
    if (duration !== undefined) pendingUpdate.current.duration = duration;

    if (!rafRef.current) {
      rafRef.current = requestAnimationFrame(flushUpdate);
    }
  }, [flushUpdate]);

  // Cleanup RAF on unmount
  useEffect(() => {
    return () => {
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
      if (wheelZoomFrameRef.current) cancelAnimationFrame(wheelZoomFrameRef.current);
    };
  }, []);

  // --- Cursor-anchored wheel zoom ---
  // Ctrl/Cmd + wheel (and Mac trackpad pinch, which browsers deliver as
  // ctrl+wheel) zooms `viewportDuration` continuously, anchored on the
  // cursor X so the moment under the cursor stays put. Bounds are looser
  // than the discrete presets so users can over- or under-shoot the Day /
  // 4 Months range a little. React's onWheel is passive, so we attach a
  // native listener to be able to preventDefault and stop browser zoom.
  //
  // `latestZoomRef` is the source of truth for the in-flight viewport during
  // a pinch / wheel gesture. We accumulate wheel delta and apply it once per
  // animation frame, so trackpad bursts do not compound several full zoom
  // steps before React gets a chance to paint. A separate useEffect re-syncs
  // the ref from committed state so external updates (preset clicks, drag pan,
  // Today button) do not get clobbered.
  const latestZoomRef = useRef({ start: viewportStart, duration: viewportDuration });
  useEffect(() => {
    latestZoomRef.current = { start: viewportStart, duration: viewportDuration };
  }, [viewportStart, viewportDuration]);

  useEffect(() => {
    if (!focusDate || appliedFocusDateRef.current === focusDate) return;

    const target = parseISO(focusDate);
    if (Number.isNaN(target.getTime())) return;

    const centeredDay = startOfDay(target);
    const centeredStart = addHours(centeredDay, 12 - viewportDuration / 2);
    appliedFocusDateRef.current = focusDate;
    setViewportStart(centeredStart);
  }, [focusDate, viewportDuration]);
  const containerWidthRef = useRef(containerWidth);
  useEffect(() => {
    containerWidthRef.current = containerWidth;
  }, [containerWidth]);
  const wheelZoomFrameRef = useRef<number | null>(null);
  const wheelZoomStateRef = useRef({ deltaY: 0, anchorRatio: 0.5 });

  const flushWheelZoom = useCallback(() => {
    wheelZoomFrameRef.current = null;
    const width = containerWidthRef.current;
    if (width <= 0) {
      wheelZoomStateRef.current.deltaY = 0;
      return;
    }

    const pendingDeltaY = wheelZoomStateRef.current.deltaY;
    if (Math.abs(pendingDeltaY) < 0.01) {
      wheelZoomStateRef.current.deltaY = 0;
      return;
    }

    const absPendingDeltaY = Math.abs(pendingDeltaY);
    const stepMagnitude = absPendingDeltaY <= 12
      ? absPendingDeltaY
      : Math.min(72, absPendingDeltaY * 0.42);
    const stepDeltaY = Math.sign(pendingDeltaY) * stepMagnitude;
    wheelZoomStateRef.current.deltaY -= stepDeltaY;
    const baseStart = latestZoomRef.current.start;
    const baseDuration = latestZoomRef.current.duration;
    const factor = Math.exp(stepDeltaY * 0.0038);
    const MIN_HOURS = 6;
    const MAX_HOURS = 24 * 120;
    const newDuration = Math.max(
      MIN_HOURS,
      Math.min(MAX_HOURS, baseDuration * factor)
    );
    if (Math.abs(newDuration - baseDuration) < 0.01) return;

    const anchorRatio = wheelZoomStateRef.current.anchorRatio;
    const cursorTimeMs = baseStart.getTime() + anchorRatio * baseDuration * HOUR_MS;
    const newStart = new Date(cursorTimeMs - anchorRatio * newDuration * HOUR_MS);

    latestZoomRef.current = { start: newStart, duration: newDuration };
    startTransition(() => {
      setViewportStart(newStart);
      setViewportDuration(newDuration);
    });

    if (Math.abs(wheelZoomStateRef.current.deltaY) > 0.01) {
      wheelZoomFrameRef.current = requestAnimationFrame(flushWheelZoom);
    }
  }, []);

  useEffect(() => {
    const node = containerRef.current;
    if (!node) return;

    const onWheel = (e: WheelEvent) => {
      if (!(e.ctrlKey || e.metaKey)) return;
      const width = containerWidthRef.current;
      if (width <= 0) return;
      e.preventDefault();

      // Normalize deltaMode (lines → pixels) so mouse wheels with deltaMode=1
      // get reasonable speed too. We then spend the accumulated delta over
      // animation frames so larger gestures keep moving smoothly instead of
      // getting clamped into one jumpy frame.
      const dy = e.deltaMode === 1
        ? e.deltaY * 20
        : e.deltaMode === 2
          ? e.deltaY * width * 1.1
          : e.deltaY * 1.85;
      const mouseX = Math.max(0, Math.min(width, e.clientX - containerLeftRef.current));

      wheelZoomStateRef.current.deltaY += dy;
      wheelZoomStateRef.current.anchorRatio = width > 0 ? mouseX / width : 0.5;

      if (!wheelZoomFrameRef.current) {
        wheelZoomFrameRef.current = requestAnimationFrame(flushWheelZoom);
      }
    };

    node.addEventListener('wheel', onWheel, { passive: false });
    return () => node.removeEventListener('wheel', onWheel);
  }, [flushWheelZoom]);

  // --- Handlers ---

  // Switch to a discrete zoom preset, preserving the current viewport center
  // so the user doesn't lose their place after a zoom change.
  const setZoomPreset = useCallback((hours: number) => {
    const center = addHours(viewportStart, viewportDuration / 2);
    setViewportStart(addHours(center, -hours / 2));
    setViewportDuration(hours);
  }, [viewportStart, viewportDuration]);

  // Recenter the viewport on the current moment without changing the zoom.
  const goToToday = useCallback(() => {
    setViewportStart(addHours(new Date(), -viewportDuration / 2));
    setGlobalStart(subDays(startOfDay(new Date()), 180));
    setGlobalEnd(addDays(startOfDay(new Date()), 180));
  }, [viewportDuration]);

  // Click-and-drag to pan the timeline
  const [isDragging, setIsDragging] = useState(false);
  const dragStartRef = useRef<{ x: number; viewportStart: Date } | null>(null);

  const handlePointerDown = useCallback((e: React.PointerEvent) => {
    // Only left mouse button, ignore if clicking on an event button
    if (e.button !== 0) return;
    const target = e.target as HTMLElement;
    if (target.closest('button')) return;

    e.preventDefault();
    containerRef.current?.setPointerCapture(e.pointerId);
    setIsDragging(true);
    dragStartRef.current = { x: e.clientX, viewportStart };
  }, [viewportStart]);

  const handlePointerMove = useCallback((e: React.PointerEvent) => {
    if (!dragStartRef.current) return;
    const deltaX = e.clientX - dragStartRef.current.x;
    const hoursDelta = -deltaX / zoom; // negative: drag right = go back in time
    const newStart = addHours(dragStartRef.current.viewportStart, hoursDelta);
    scheduleUpdate(newStart);
  }, [zoom, scheduleUpdate]);

  const handlePointerUp = useCallback((e: React.PointerEvent) => {
    if (!dragStartRef.current) return;
    containerRef.current?.releasePointerCapture(e.pointerId);
    dragStartRef.current = null;
    setIsDragging(false);
  }, []);

  const toggleFilter = (type: DisplayedEventType) => {
    setActiveFilters(prev =>
      prev.includes(type) ? prev.filter(t => t !== type) : [...prev, type]
    );
  };

  // --- Slider Logic ---
  const sliderRef = useRef<HTMLDivElement>(null);

  const getSliderMetrics = useCallback(() => {
    const totalGlobalHours = differenceInHours(globalEnd, globalStart);
    const startOffsetHours = differenceInHours(viewportStart, globalStart);
    const leftPercent = Math.max(0, (startOffsetHours / totalGlobalHours) * 100);
    const widthPercent = Math.min(100 - leftPercent, (viewportDuration / totalGlobalHours) * 100);
    return { leftPercent, widthPercent, totalGlobalHours };
  }, [viewportStart, viewportDuration, globalStart, globalEnd]);

  // Drag the slider knob to pan the viewport across the global range. Resize
  // handles were removed when we moved to discrete zoom presets — width is
  // now driven by the active preset, only position changes here.
  const handleSliderInteraction = useCallback((e: React.PointerEvent) => {
    e.preventDefault();
    e.stopPropagation();
    const slider = sliderRef.current;
    if (!slider) return;

    slider.setPointerCapture(e.pointerId);

    const startX = e.clientX;
    const { leftPercent: startLeft, widthPercent: startWidth, totalGlobalHours } = getSliderMetrics();

    const onPointerMove = (moveEvent: PointerEvent) => {
      const rect = slider.getBoundingClientRect();
      const deltaX = moveEvent.clientX - startX;
      const deltaPercent = (deltaX / rect.width) * 100;
      const newLeft = Math.max(0, Math.min(100 - startWidth, startLeft + deltaPercent));
      const newStartHours = (newLeft / 100) * totalGlobalHours;
      scheduleUpdate(addHours(globalStart, newStartHours));
    };

    const onPointerUp = (upEvent: PointerEvent) => {
      slider.releasePointerCapture(upEvent.pointerId);
      window.removeEventListener('pointermove', onPointerMove);
      window.removeEventListener('pointerup', onPointerUp);
    };

    window.addEventListener('pointermove', onPointerMove);
    window.addEventListener('pointerup', onPointerUp);
  }, [getSliderMetrics, scheduleUpdate, globalStart]);

  const { leftPercent, widthPercent } = getSliderMetrics();

  const isDayZoom = viewportDuration <= 24 * 1.5;

  // --- Dynamic Ticks & Labels (memoized) ---
  // Precompute dateMs + label + isMajor once per viewport change; the render
  // loop then only does a numeric subtraction and multiply per tick.
  const { ticks, tickType } = useMemo(() => {
    const daysVisible = viewportDuration / 24;
    const isYearView = daysVisible > 60;
    const isMonthView = daysVisible > 14;

    let tickType: 'hour' | 'day' | 'month' = 'hour';
    if (isYearView) tickType = 'month';
    else if (isMonthView) tickType = 'day';

    type Tick = { dateMs: number; label: string; isMajor: boolean };
    const ticks: Tick[] = [];
    const viewStart = viewportStart;
    const viewEnd = addHours(viewportStart, viewportDuration);

    if (tickType === 'month') {
      let current = startOfMonth(viewStart);
      if (current < viewStart) current = addMonths(current, 1);
      current = addMonths(current, -1);
      while (current < viewEnd) {
        ticks.push({
          dateMs: current.getTime(),
          label: format(current, "MMMM yyyy"),
          isMajor: true,
        });
        current = addMonths(current, 1);
      }
    } else if (tickType === 'day') {
      let current = startOfDay(viewStart);
      current = addDays(current, -1);
      while (current < viewEnd) {
        const isFirstOfMonth = current.getDate() === 1;
        ticks.push({
          dateMs: current.getTime(),
          label: isFirstOfMonth ? format(current, "MMM d") : format(current, "d"),
          isMajor: isFirstOfMonth,
        });
        current = addDays(current, 1);
      }
    } else {
      let interval = 1;
      if (daysVisible > 2) interval = 4;
      if (daysVisible > 5) interval = 12;

      let current = startOfDay(viewStart);
      while (differenceInHours(current, viewStart) > -24) current = addHours(current, -interval);
      while (current < viewEnd) {
        const isDayStart = current.getHours() === 0;
        ticks.push({
          dateMs: current.getTime(),
          label: isDayStart ? format(current, "MMM d") : format(current, "HH:mm"),
          isMajor: isDayStart,
        });
        current = addHours(current, interval);
      }
    }
    return { ticks, tickType };
  }, [viewportStart, viewportDuration]);


  // Today line position — plain numeric math, no date-fns allocation.
  const todayLeft = ((Date.now() - viewportStartMs) / HOUR_MS) * zoom;

  return (
    <div 
      className={cn(
        "w-full bg-white rounded-2xl shadow-sm border border-slate-100 flex flex-col overflow-hidden select-none",
        fill && "h-full",
        "focus:outline-none focus:ring-2 focus:ring-blue-500/20"
      )}
      tabIndex={0}
      onKeyDown={(e) => {
        if (e.ctrlKey) {
          if (e.key === 'ArrowLeft') {
            e.preventDefault();
            setViewportStart((prev) => new Date(prev.getTime() - viewportDuration * 0.1 * HOUR_MS));
          } else if (e.key === 'ArrowRight') {
            e.preventDefault();
            setViewportStart((prev) => new Date(prev.getTime() + viewportDuration * 0.1 * HOUR_MS));
          }
        }
      }}
    >
      {/* Header Controls */}
      {isMinimized ? (
        <button
          type="button"
          onClick={toggleMinimize}
          className="group flex w-full items-center justify-between gap-4 bg-white px-5 py-3 text-left transition-colors hover:bg-slate-50"
          title="Show Timeline"
        >
          <div className="flex min-w-0 items-center gap-3">
            <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-blue-50 text-blue-600 transition-colors group-hover:bg-blue-100">
              <Calendar size={16} />
            </div>
            <div className="min-w-0">
              <h2 className="text-sm font-semibold text-slate-800">Timeline</h2>
              <p className="truncate text-xs text-slate-500">
                Centered on {format(addHours(viewportStart, viewportDuration / 2), "MMM d, yyyy")}
              </p>
            </div>
          </div>
          <div className="flex shrink-0 items-center gap-1.5 rounded-lg border border-slate-200 bg-white px-3 py-1.5 text-xs font-semibold text-slate-600 shadow-sm transition-colors group-hover:border-blue-300 group-hover:text-blue-600">
            Show
            <ChevronDown size={14} />
          </div>
        </button>
      ) : (
        <div className="px-6 py-3 border-b border-slate-100 flex flex-col md:flex-row md:items-center justify-between gap-3 bg-white z-20">
          {/* Left: title + Today */}
          <div className="flex items-center gap-3 min-w-0">
            <button
              onClick={toggleMinimize}
              className="p-2 bg-blue-50 hover:bg-blue-100 rounded-lg text-blue-600 transition-colors shrink-0"
              title="Hide Timeline"
            >
              <Calendar size={20} />
            </button>

            <div className="min-w-0">
              <h2 className="text-sm font-semibold text-slate-800 leading-tight">Timeline</h2>
            </div>

            <button
              onClick={goToToday}
              className="ml-1 px-3 py-1.5 text-xs font-semibold text-slate-700 bg-white border border-slate-200 rounded-lg hover:bg-slate-50 hover:border-slate-300 transition-colors shrink-0"
              title="Jump to today"
            >
              Today
            </button>
          </div>

          {/* Right: filters + zoom presets + maximize */}
          <div className="flex items-center gap-2 flex-wrap">
            {/* Type filters */}
            <div className="flex items-center gap-0.5 rounded-lg border border-slate-200 bg-slate-100 p-0.5">
              {(Object.keys(ICON_MAP) as DisplayedEventType[]).map((type) => {
                const T = type;
                const Icon = ICON_MAP[T];
                const isActive = activeFilters.length === 0 || activeFilters.includes(T);
                
                const iconColor = {
                  appointments: "text-blue-600",
                  labs: "text-rose-600",
                  prescriptions: "text-emerald-600",
                  vaccinations: "text-purple-600",
                  imaging: "text-sky-600",
                }[T] || "text-slate-600";

                return (
                  <button
                    key={type}
                    onClick={() => toggleFilter(T)}
                    className={cn(
                      "flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-all",
                      isActive
                        ? "bg-white text-slate-900 shadow-sm"
                        : "text-slate-500 hover:text-slate-800"
                    )}
                    title={`Toggle ${type}`}
                  >
                    <Icon size={13} strokeWidth={2} className={isActive ? iconColor : "text-slate-400"} />
                    <span className="hidden xl:block capitalize">{type.replace('-', ' ')}</span>
                  </button>
                );
              })}
            </div>

            {/* Zoom preset group */}
            <div className="flex items-center gap-0.5 bg-slate-50 p-1 rounded-xl border border-slate-100">
              {ZOOM_PRESETS.map((p) => (
                <button
                  key={p.id}
                  onClick={() => setZoomPreset(p.hours)}
                  className={cn(
                    "px-2.5 py-1.5 text-xs font-medium rounded-lg transition-all whitespace-nowrap",
                    viewportDuration === p.hours
                      ? "bg-white shadow-sm text-slate-800"
                      : "text-slate-500 hover:text-slate-700"
                  )}
                  title={`${p.label} view`}
                >
                  {p.label}
                </button>
              ))}
            </div>

            <button
              onClick={() => setCurrentSize(prev => prev === 'large' ? 'default' : 'large')}
              className="p-2 bg-slate-50 hover:bg-white border border-slate-100 rounded-lg shadow-sm text-slate-500 transition-colors"
              title={currentSize === 'large' ? "Collapse" : "Expand"}
            >
              {currentSize === 'large' ? <Minimize2 size={16} /> : <Maximize2 size={16} />}
            </button>
          </div>
        </div>
      )}

      {/* Main Timeline Area */}
      <div
        ref={containerRef}
        className={cn(
          "relative bg-slate-50/20 overflow-hidden",
          // In fill mode the events area expands to fill the outer flex column
          // (the parent gives us h-full). Without fill, an explicit pixel
          // height is set via heightStyle below.
          fill && !isMinimized && "flex-1 min-h-0",
          isDragging ? "cursor-grabbing" : "cursor-grab",
          isMinimized ? "opacity-0" : "opacity-100"
        )}
        style={{
          ...heightStyle,
          transition: 'height 0.3s ease, opacity 0.3s ease',
          willChange: 'contents',
          touchAction: 'none',
        }}
        onPointerDown={handlePointerDown}
        onPointerMove={handlePointerMove}
        onPointerUp={handlePointerUp}
        onPointerCancel={handlePointerUp}
      >
        {/* Ticks & Grid — plain divs, no transitions during pan.
            Hot path: precomputed dateMs + label, pure numeric subtract/multiply. */}
        {ticks.map((tick, i) => {
          const left = ((tick.dateMs - viewportStartMs) / HOUR_MS) * zoom;
          // Cheap culling: tick is a 1px line but its label can hang off it, so
          // keep a small buffer either side.
          if (left < -80 || left > containerWidth + 80) return null;

          return (
            <div
              key={`${tickType}-${i}`}
              className="absolute top-0 bottom-0 pointer-events-none"
              style={{ transform: `translate3d(${left}px, 0, 0)` }}
            >
              <div
                className={cn(
                  "absolute inset-y-0 left-0",
                  tick.isMajor
                    ? isDayZoom
                      ? "w-px bg-slate-200/80"
                      : "w-px bg-slate-300/80"
                    : isDayZoom
                      ? "w-px bg-slate-200/35"
                      : "w-px bg-slate-200/55"
                )}
              />
              <div className={cn(
                "absolute top-2 left-2 whitespace-nowrap rounded-full px-2 py-0.5 font-medium",
                tick.isMajor
                  ? "bg-white/85 text-xs text-slate-600 shadow-sm"
                  : "bg-white/70 text-[10px] text-slate-400"
              )}>
                {tick.label}
              </div>
            </div>
          );
        })}

        {/* Today Line */}
        {todayLeft >= -10 && todayLeft <= containerWidth + 10 && (
          <div
            className="absolute top-0 bottom-0 border-r-2 border-blue-500 pointer-events-none z-20"
            style={{ transform: `translate3d(${todayLeft}px, 0, 0)` }}
          >
            <div className="absolute top-2 left-1 text-xs font-bold text-blue-600 bg-blue-50 px-2 py-0.5 rounded-md whitespace-nowrap shadow-sm">
              Today
            </div>
            <div
              className="absolute top-0 left-0 w-0 h-0 border-l-[6px] border-l-transparent border-r-[6px] border-r-transparent border-t-[8px] border-t-blue-500"
              style={{ transform: 'translateX(-6px)' }}
            />
          </div>
        )}

        {/* Events Layer — stable anchors and stable lanes.
            Cards always stay attached to the same timeline point and extend in
            one consistent direction, so pan / zoom changes scale the layout
            without re-grouping or side-switching items. */}
        <div
          className={cn(
            "absolute left-0 right-0 bottom-0",
            needsVerticalScroll && "overflow-y-auto overflow-x-hidden"
          )}
          style={{ top: `${topOffset}px` }}
        >
          <div
            className="relative w-full"
            style={{ height: needsVerticalScroll ? `${scrollContentHeight}px` : '100%' }}
          >
            {Array.from({ length: totalRows }).map((_, row) => (
              <div
                key={`lane-${row}`}
                className="pointer-events-none absolute left-0 right-0 border-t border-dashed border-slate-200/70"
                style={{ top: `${eventsOffsetY + row * rowHeight + rowHeight / 2}px` }}
              />
            ))}

            {renderedItems.map((renderedItem) => {
              const displayRow = renderedItem.displayRow;

              if (renderedItem.kind === 'group') {
                const groupCenterPx = ((renderedItem.anchorMs - viewportStartMs) / HOUR_MS) * zoom;
                const groupLeftPx = groupCenterPx - renderedItem.sizePx / 2;
                const groupRightPx = groupCenterPx + renderedItem.sizePx / 2;
                if (groupRightPx < -120 || groupLeftPx > containerWidth + 120) return null;

                const groupTop = eventsOffsetY + displayRow * rowHeight + Math.max(0, (rowHeight - renderedItem.sizePx) / 2);
                const groupTypeCounts = Array.from(
                  renderedItem.items.reduce((map, entry) => {
                    const type = entry.event.type as DisplayedEventType;
                    map.set(type, (map.get(type) ?? 0) + 1);
                    return map;
                  }, new Map<DisplayedEventType, number>())
                )
                  .map(([type, count]) => ({ type, count }))
                  .sort((left, right) => right.count - left.count || left.type.localeCompare(right.type))
                  .slice(0, 3);
                const groupSignature = groupTypeCounts.map(({ type, count }) => `${type}:${count}`).join('|');
                const groupCountTextClass = renderedItem.sizePx >= 48 ? "text-[18px]" : "text-[16px]";
                const groupRackClass = renderedItem.sizePx >= 48 ? "gap-1.5 px-2 py-1" : "gap-1 px-1.5 py-0.5";
                const groupIndicatorSize = renderedItem.sizePx >= 48 ? 8 : 7;
	                return (
	                  <Popover key={renderedItem.motionKey}>
	                    <PopoverTrigger asChild>
	                      <button
                        className={BAR_BUTTON_BASE}
                        style={{
                          transform: `translate3d(${groupLeftPx}px, ${groupTop}px, 0)`,
	                          width: `${renderedItem.sizePx}px`,
	                          height: `${renderedItem.sizePx}px`,
	                        }}
	                      >
	                        <TimelineAnimatedSurface
	                          className="absolute inset-0 overflow-visible transition-colors"
	                          displayRow={displayRow}
	                          layoutBox={{ left: groupLeftPx, top: groupTop, width: renderedItem.sizePx, height: renderedItem.sizePx }}
	                          layoutModeKey="group"
	                          rowHeight={rowHeight}
	                          visualSignature={`group:${renderedItem.items.length}:${groupSignature}`}
	                        >
                            <span
                              className="pointer-events-none absolute inset-[6px] rounded-full border border-slate-200/80 bg-white/70 shadow-[0_6px_14px_rgba(15,23,42,0.05)]"
                              style={{ transform: 'translate(-14%, 10%) scale(0.9)' }}
                            />
                            <span
                              className="pointer-events-none absolute inset-[6px] rounded-full border border-slate-200/85 bg-white/78 shadow-[0_8px_16px_rgba(15,23,42,0.06)]"
                              style={{ transform: 'translate(14%, 10%) scale(0.9)' }}
                            />
                            <span className="relative z-10 flex h-full w-full flex-col items-center justify-center rounded-full border-2 border-slate-200 bg-white/95 shadow-[0_10px_24px_rgba(15,23,42,0.08)] backdrop-blur-sm transition-colors hover:border-slate-300">
                              <span className={cn("font-semibold leading-none text-slate-700", groupCountTextClass)}>
                                {renderedItem.items.length}
                              </span>
	                            <span className={cn(
                                "mt-1.5 flex items-center rounded-full bg-slate-50/90 shadow-[inset_0_1px_0_rgba(255,255,255,0.92)]",
                                groupRackClass
                              )}>
                                {groupTypeCounts.map(({ type, count }) => (
                                  <span
                                    key={type}
                                    className={cn("block rounded-full ring-2 ring-white", EVENT_DOT_CLASS_MAP[type])}
                                    style={{
                                      width: `${Math.min(12, groupIndicatorSize + (count - 1) * 1.5)}px`,
                                      height: `${groupIndicatorSize}px`,
                                    }}
                                  />
                                ))}
	                            </span>
                            </span>
	                        </TimelineAnimatedSurface>
	                      </button>
	                    </PopoverTrigger>
                    <PopoverContent side="top" align="center" className="w-[360px]">
                      <GroupedEventsPopoverContent events={renderedItem.items.map((entry) => entry.event as DisplayedTimelineEvent)} />
                    </PopoverContent>
                  </Popover>
                );
              }

              const primary = renderedItem.item;
              const event = primary.event as DisplayedTimelineEvent;
              const Icon = ICON_MAP[event.type];
              const iconWrapSize = Math.max(26, Math.min(36, eventNodeHeight - 8));

              if (renderedItem.kind === 'medication') {
                const medStartPx = ((renderedItem.renderStartMs - viewportStartMs) / HOUR_MS) * zoom;
                const medWidthPx = Math.max(2, ((renderedItem.renderEndMs - renderedItem.renderStartMs) / HOUR_MS) * zoom);
                const medEndPx = medStartPx + medWidthPx;
                if (medEndPx < -120 || medStartPx > containerWidth + 120) return null;

                const top = eventsOffsetY + displayRow * rowHeight + eventVerticalPadding;
                const showSubtext = medicationDetailMode === 'full';
                const showLabel = medWidthPx >= 92;
                const showIcon = medWidthPx >= 24;
                const showWideContent = medWidthPx >= medicationCardWidth;

                return (
                  <Popover key={renderedItem.motionKey}>
	                    <PopoverTrigger asChild>
	                      <button
                        className={primary.buttonClass}
                        style={{
                          transform: `translate3d(${medStartPx}px, ${top}px, 0)`,
	                          width: `${medWidthPx}px`,
	                          height: `${eventNodeHeight}px`,
	                        }}
	                      >
	                        <TimelineAnimatedSurface
	                          className={cn(
	                            primary.capsuleClass,
	                            "absolute inset-0 flex items-center rounded-[999px] px-3 text-left shadow-sm backdrop-blur-sm"
	                          )}
	                          displayRow={displayRow}
	                          layoutBox={{ left: medStartPx, top, width: medWidthPx, height: eventNodeHeight }}
	                          layoutModeKey={`medication:${medicationDetailMode}`}
	                          rowHeight={rowHeight}
	                          visualSignature={`medication:${medicationDetailMode}`}
	                        >
	                          {showIcon && (
		                            <span className={cn("flex shrink-0 items-center justify-center rounded-full bg-transparent", primary.textColor)} style={{ width: `${iconWrapSize}px`, height: `${iconWrapSize}px` }}>
		                              <Icon size={17} strokeWidth={2.2} />
                            </span>
                          )}
                          {showLabel && (
                            <span className={cn("min-w-0", showIcon ? "ml-2.5" : "")}>
                              <span className={`block truncate text-left font-semibold ${primary.textColor} ${labelTextClass}`}>
                                {primary.label}
                              </span>
                              {showSubtext && showWideContent && (
                                <span className="mt-0.5 block truncate text-[11px] text-slate-500">
                                  {primary.subtext}
                                </span>
	                              )}
	                            </span>
	                          )}
	                        </TimelineAnimatedSurface>
	                      </button>
	                    </PopoverTrigger>
                    <PopoverContent side="top" align="start" className="w-[340px]">
                      <EventPopoverContent event={event} />
                    </PopoverContent>
                  </Popover>
                );
              }

              const pointMode = renderedItem.pointMode ?? pointVisualMode;
              if (pointMode === 'dot') {
                const pointCenterPx = ((renderedItem.anchorMs - viewportStartMs) / HOUR_MS) * zoom;
                const pointLeftPx = pointCenterPx - pointDotSize / 2;
                const pointRightPx = pointCenterPx + pointDotSize / 2;
                if (pointRightPx < -120 || pointLeftPx > containerWidth + 120) return null;

                const pointTop = eventsOffsetY + displayRow * rowHeight + Math.max(0, (rowHeight - pointDotSize) / 2);
                return (
                  <Popover key={renderedItem.motionKey}>
	                    <PopoverTrigger asChild>
	                      <button
                        className={primary.buttonClass}
                        style={{
                          transform: `translate3d(${pointLeftPx}px, ${pointTop}px, 0)`,
	                          width: `${pointDotSize}px`,
	                          height: `${pointDotSize}px`,
	                        }}
	                      >
	                        <TimelineAnimatedSurface
	                          className={cn(
	                            primary.capsuleClass,
	                            "absolute inset-0 flex items-center justify-center shadow-[0_10px_24px_rgba(15,23,42,0.08)] backdrop-blur-sm"
	                          )}
	                          displayRow={displayRow}
	                          layoutBox={{ left: pointLeftPx, top: pointTop, width: pointDotSize, height: pointDotSize }}
	                          layoutModeKey={`point:${pointMode}`}
	                          rowHeight={rowHeight}
	                          visualSignature={`point:${pointMode}:${event.type}`}
	                        >
			                          <Icon className={primary.textColor} size={pointDotSize >= 50 ? 19 : 17} strokeWidth={2.2} />
	                        </TimelineAnimatedSurface>
	                      </button>
	                    </PopoverTrigger>
                    <PopoverContent side="top" align="center" className="w-[340px]">
                      <EventPopoverContent event={event} />
                    </PopoverContent>
                  </Popover>
                );
              }

              const pointLeftPx = ((renderedItem.renderStartMs - viewportStartMs) / HOUR_MS) * zoom;
              const pointWidthPx = Math.max(2, ((renderedItem.renderEndMs - renderedItem.renderStartMs) / HOUR_MS) * zoom);
              const pointRightPx = pointLeftPx + pointWidthPx;
              if (pointRightPx < -120 || pointLeftPx > containerWidth + 120) return null;

              const top = eventsOffsetY + displayRow * rowHeight + eventVerticalPadding;
              const showIcon = pointWidthPx >= 24;
              const showLabel = pointWidthPx >= 92;
              const showSubtext = pointMode === 'timed' && pointWidthPx >= 168;
              const cardHeight = eventNodeHeight;
              const cardTop = (eventNodeHeight - cardHeight) / 2;

              return (
                <Popover key={renderedItem.motionKey}>
	                  <PopoverTrigger asChild>
	                    <button
                      className={primary.buttonClass}
                      style={{
                        transform: `translate3d(${pointLeftPx}px, ${top}px, 0)`,
	                        width: `${pointWidthPx}px`,
	                        height: `${eventNodeHeight}px`,
	                      }}
	                    >
	                      <TimelineAnimatedSurface
	                        className={cn(
	                          primary.capsuleClass,
	                          "absolute flex items-center gap-3 rounded-[999px] px-4 text-left shadow-sm backdrop-blur-sm"
	                        )}
	                        displayRow={displayRow}
	                        layoutBox={{ left: pointLeftPx, top: top + cardTop, width: pointWidthPx, height: cardHeight }}
	                        layoutModeKey={`point:${pointMode}`}
	                        rowHeight={rowHeight}
	                        style={{
	                          left: 0,
	                          width: `${pointWidthPx}px`,
	                          top: `${cardTop}px`,
	                          height: `${cardHeight}px`,
	                        }}
	                        visualSignature={`point:${pointMode}:${showIcon ? 1 : 0}:${showLabel ? 1 : 0}:${showSubtext ? 1 : 0}`}
	                      >
	                        {showIcon && (
	                          <span className={cn("flex shrink-0 items-center justify-center rounded-full bg-transparent", primary.textColor)} style={{ width: `${iconWrapSize}px`, height: `${iconWrapSize}px` }}>
	                            <Icon size={17} strokeWidth={2.2} />
                          </span>
                        )}
                        {showLabel && (
                          <span className="min-w-0">
                            <span className={`block truncate text-left font-semibold ${primary.textColor} ${labelTextClass}`}>
                              {primary.label}
                            </span>
                            {showSubtext && (
                              <span className="mt-0.5 block truncate text-[11px] text-slate-500">
                                {primary.subtext}
                              </span>
	                            )}
	                          </span>
	                        )}
	                      </TimelineAnimatedSurface>
	                    </button>
	                  </PopoverTrigger>
                  <PopoverContent side="top" align="start" className="w-[340px]">
                    <EventPopoverContent event={event} />
                  </PopoverContent>
                </Popover>
              );
            })}
          </div>
        </div>
      </div>

      {/* Bottom Slider / Minimap — drag the knob to pan across the global
          range. Width is locked to the active zoom preset (no resize handles). */}
      {!isMinimized && (
        <div className="h-12 border-t border-slate-100 bg-white px-6 py-2.5 relative flex items-center">
          <div ref={sliderRef} className="w-full h-2 bg-slate-100 rounded-full relative overflow-visible">
            <div
              className="absolute top-1/2 -translate-y-1/2 h-7 bg-blue-500/10 border border-blue-500 rounded-md cursor-grab active:cursor-grabbing hover:bg-blue-500/20 transition-colors"
              style={{ left: `${leftPercent}%`, width: `${widthPercent}%` }}
              onPointerDown={(e) => handleSliderInteraction(e)}
            />
           </div>
        </div>
      )}
    </div>
  );
}
