import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import {
  addDays,
  format,
  isSameDay,
  isToday,
  parseISO,
  startOfToday,
  startOfWeek,
} from "date-fns";
import {
  Calendar,
  ChevronDown,
  ChevronLeft,
  ChevronRight,
  Maximize2,
  Minimize2,
  Pill,
  Stethoscope,
  Syringe,
  Beaker,
  ImageIcon,
  type LucideIcon,
} from "lucide-react";
import { cn } from "../utils/helpers";
import { hasSpecificTime } from "../utils/eventTime";
import { useHealthStore } from "../store/useHealthStore";
import type {
  Appointment,
  EventType,
  ImagingEvent,
  LabResult,
  TimelineEvent,
  VaccinationEvent,
} from "../types/medical";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "./ui/popover";
import { EventPopoverContent } from "./EventPopoverContent";

type DisplayedEventType = Exclude<EventType, "vitals">;
type DisplayedTimelineEvent = Exclude<TimelineEvent, { type: "vitals" }>;
type TimedEvent = Appointment | LabResult | VaccinationEvent | ImagingEvent;

const DAYS_IN_WEEK = 7;
const HOURS = Array.from({ length: 24 }, (_, hour) => hour);
const DAY_COLUMN_MIN_WIDTH = 132;
const TIME_GUTTER_WIDTH = 76;
const ALL_DAY_ROW_HEIGHT = 22;
const BLOCK_GAP = 6;
// Vertical-zoom bounds — slot height (one hour row) clamped to this range
// regardless of base fit + user zoom multiplier. The floor is intentionally
// tiny so the user can compress all 24 hours into a sliver of the viewport
// (hour labels step at small sizes — see `labelStep` below).
const MIN_SLOT_HEIGHT = 6;
const MAX_SLOT_HEIGHT = 160;

// Event color mapping kept consistent with FullScreenCalendar so the two
// views read as the same data. Bars use a flat fill + hairline border (no
// stripes/accents/shadows — see feedback_timeline_node_design.md), and the
// icon next to the centered title uses the matching tone.
const EVENT_META: Record<
  DisplayedEventType,
  {
    icon: LucideIcon;
    label: string;
    bar: string;
    iconClass: string;
    badgeClass: string;
  }
> = {
  appointments: {
    icon: Stethoscope,
    label: "Appointment",
    bar: "border-blue-200/80 bg-blue-50 hover:bg-blue-100/70",
    iconClass: "text-blue-600",
    badgeClass: "badge badge-blue",
  },
  labs: {
    icon: Beaker,
    label: "Lab",
    bar: "border-rose-200/80 bg-rose-50 hover:bg-rose-100/70",
    iconClass: "text-rose-600",
    badgeClass: "badge badge-red",
  },
  prescriptions: {
    icon: Pill,
    label: "Prescription",
    bar: "border-emerald-200/80 bg-emerald-50 hover:bg-emerald-100/70",
    iconClass: "text-emerald-600",
    badgeClass: "badge badge-green",
  },
  vaccinations: {
    icon: Syringe,
    label: "Vaccination",
    bar: "border-purple-200/80 bg-purple-50 hover:bg-purple-100/70",
    iconClass: "text-purple-600",
    badgeClass: "badge badge-purple",
  },
  imaging: {
    icon: ImageIcon,
    label: "Imaging",
    bar: "border-sky-200/80 bg-sky-50 hover:bg-sky-100/70",
    iconClass: "text-sky-600",
    badgeClass: "badge badge-blue",
  },
};

interface SchedulerProps {
  size?: "default" | "large";
  fill?: boolean;
  initialMinimized?: boolean;
  isMinimized?: boolean;
  onToggleMinimize?: () => void;
}

type TimedLayout = {
  event: TimedEvent;
  dayIndex: number;
  lane: number;
  laneCount: number;
  startMinutes: number;
  endMinutes: number;
};

type AllDayLayout = {
  event: DisplayedTimelineEvent;
  startIndex: number;
  endIndex: number;
  lane: number;
};

const parseTimeToMinutes = (
  time: string | undefined,
  fallbackMinutes: number
): number => {
  if (!time) 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));
};

const getTimedEventStart = (event: TimedEvent) => {
  if (event.type === "appointments") return parseTimeToMinutes(event.time, 9 * 60);
  if (event.type === "vaccinations") return parseTimeToMinutes(event.time, 10 * 60);
  if (event.type === "imaging") return parseTimeToMinutes(event.time, 13 * 60);
  return parseTimeToMinutes(event.time, 11 * 60);
};

const getTimedEventDuration = (event: TimedEvent) =>
  event.type === "appointments" ? 60 : event.type === "imaging" ? 45 : 30;

const getWeekLabel = (weekStart: Date) => {
  const weekEnd = addDays(weekStart, DAYS_IN_WEEK - 1);
  const sameMonth = format(weekStart, "MMM") === format(weekEnd, "MMM");
  if (sameMonth) return `${format(weekStart, "MMMM d")} - ${format(weekEnd, "d, yyyy")}`;
  return `${format(weekStart, "MMM d")} - ${format(weekEnd, "MMM d, yyyy")}`;
};

const getEventTitle = (event: DisplayedTimelineEvent) => {
  if (event.type === "appointments") return event.reason;
  if (event.type === "labs") return event.testName;
  if (event.type === "imaging") return (event as ImagingEvent).name;
  return event.name;
};

const formatMinutesLabel = (minutes: number) =>
  format(
    new Date().setHours(Math.floor(minutes / 60), minutes % 60, 0, 0),
    "HH:mm"
  );

export function Scheduler({
  size = "default",
  fill = false,
  initialMinimized = false,
  isMinimized: propIsMinimized,
  onToggleMinimize: propOnToggleMinimize,
}: SchedulerProps) {
  const [currentSize, setCurrentSize] = useState(size);
  const [internalMinimized, setInternalMinimized] = useState(initialMinimized);
  const [activeFilters, setActiveFilters] = useState<DisplayedEventType[]>([]);
  const today = startOfToday();
  const [weekStart, setWeekStart] = useState(() =>
    startOfWeek(today, { weekStartsOn: 1 })
  );
  const viewportRef = useRef<HTMLDivElement>(null);
  const scrollRef = useRef<HTMLDivElement>(null);
  const stickyHeaderRef = useRef<HTMLDivElement>(null);
  const [viewportHeight, setViewportHeight] = useState(0);
  const [stickyHeaderHeight, setStickyHeaderHeight] = useState(0);
  // User vertical-zoom multiplier — driven by ctrl/cmd + wheel and Mac
  // trackpad pinch (browsers deliver pinch as ctrl+wheel). Multiplies the
  // base auto-fit slot height; clamped via MIN_SLOT_HEIGHT/MAX_SLOT_HEIGHT.
  const [userZoom, setUserZoom] = useState(1);

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

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

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

  const visibleEvents = useMemo(() => {
    const now = Date.now();
    const merged: DisplayedTimelineEvent[] = [
      ...appointments,
      ...medications,
      ...labResults,
      ...(vaccinations || []),
      ...(imaging || []),
    ];

    const timeFiltered = merged.filter((event) => {
      if ((event.type === "labs" || event.type === "imaging") && new Date(event.date).getTime() > now) return false;
      return true;
    });

    if (activeFilters.length === 0) return timeFiltered;
    return timeFiltered.filter((event) => activeFilters.includes(event.type));
  }, [activeFilters, appointments, labResults, medications, vaccinations, imaging]);

  const weekDays = useMemo(
    () => Array.from({ length: DAYS_IN_WEEK }, (_, index) => addDays(weekStart, index)),
    [weekStart]
  );

  const timedLayouts = useMemo<TimedLayout[]>(() => {
    const layouts: TimedLayout[] = [];

    weekDays.forEach((day, dayIndex) => {
      const dayEvents = visibleEvents.filter(
        (event): event is TimedEvent =>
          event.type !== "prescriptions" &&
          "time" in event &&
          hasSpecificTime(event.time) &&
          isSameDay(parseISO(event.date), day)
      );

      const items = dayEvents
        .map((event) => {
          const startMinutes = getTimedEventStart(event);
          const endMinutes = Math.min(
            24 * 60,
            startMinutes + getTimedEventDuration(event)
          );
          return { event, startMinutes, endMinutes };
        })
        .sort((a, b) => a.startMinutes - b.startMinutes);

      const laneEnds: number[] = [];
      const positioned = items.map((item) => {
        let lane = 0;
        while (
          lane < laneEnds.length &&
          laneEnds[lane] > item.startMinutes + 5
        ) {
          lane += 1;
        }
        laneEnds[lane] = item.endMinutes;
        return { ...item, lane };
      });

      const laneCount = Math.max(1, laneEnds.length);

      positioned.forEach((item) => {
        layouts.push({
          event: item.event,
          dayIndex,
          lane: item.lane,
          laneCount,
          startMinutes: item.startMinutes,
          endMinutes: item.endMinutes,
        });
      });
    });

    return layouts;
  }, [visibleEvents, weekDays]);

  const allDayLayouts = useMemo<AllDayLayout[]>(() => {
    const weekEnd = addDays(weekStart, DAYS_IN_WEEK - 1);
    const overlaps: Array<Omit<AllDayLayout, "lane">> = [];

    visibleEvents.forEach((event) => {
      if (event.type !== "prescriptions") {
        if (!("time" in event) || hasSpecificTime(event.time)) return;

        const eventDate = parseISO(event.date);
        if (eventDate < weekStart || eventDate > weekEnd) return;

        const dayIndex = weekDays.findIndex((day) => isSameDay(day, eventDate));
        if (dayIndex === -1) return;

        overlaps.push({
          event,
          startIndex: dayIndex,
          endIndex: dayIndex,
        });
        return;
      }

      const start = parseISO(event.startDate || event.date);
      const rawEnd = parseISO(event.endDate || event.date);
      const end = rawEnd < start ? start : rawEnd;
      if (end < weekStart || start > weekEnd) return;

      const clampedStart = start < weekStart ? weekStart : start;
      const clampedEnd = end > weekEnd ? weekEnd : end;
      const startIndex = weekDays.findIndex((day) => isSameDay(day, clampedStart));
      const endIndex = weekDays.findIndex((day) => isSameDay(day, clampedEnd));
      if (startIndex === -1 || endIndex === -1) return;

      overlaps.push({
        event,
        startIndex,
        endIndex,
      });
    });

    overlaps.sort((a, b) => a.startIndex - b.startIndex || a.endIndex - b.endIndex);

    const laneEnds: number[] = [];
    return overlaps.map((item) => {
      let lane = 0;
      while (lane < laneEnds.length && laneEnds[lane] >= item.startIndex) {
        lane += 1;
      }
      laneEnds[lane] = item.endIndex;
      return { ...item, lane };
    });
  }, [visibleEvents, weekDays, weekStart]);

  const allDayLaneCount = Math.max(
    1,
    allDayLayouts.reduce((max, item) => Math.max(max, item.lane + 1), 0)
  );

  const hasVisibleEvents =
    timedLayouts.length > 0 || allDayLayouts.length > 0;

  useEffect(() => {
    const viewport = viewportRef.current;
    const stickyHeader = stickyHeaderRef.current;
    if (!viewport || !stickyHeader) return;

    const syncHeights = () => {
      setViewportHeight(viewport.clientHeight);
      setStickyHeaderHeight(stickyHeader.getBoundingClientRect().height);
    };

    syncHeights();

    const observer = new ResizeObserver(syncHeights);
    observer.observe(viewport);
    observer.observe(stickyHeader);

    return () => observer.disconnect();
  }, []);

  const allDayHeight = fill
    ? Math.max(28, allDayLaneCount * ALL_DAY_ROW_HEIGHT + 6)
    : Math.max(38, allDayLaneCount * ALL_DAY_ROW_HEIGHT + 10);

  // Base slot height — what the layout naturally fits at userZoom = 1.
  // In fill mode this is the EXACT auto-fit value (no clamp) so the entire
  // 24h grid sits flush in the available viewport; otherwise it's the fixed
  // default for the size.
  const baseSlotHeight = useMemo(() => {
    if (!fill) return currentSize === "large" ? 54 : 46;
    if (viewportHeight <= 0 || stickyHeaderHeight <= 0) return 22;
    const availableHeight = viewportHeight - stickyHeaderHeight - 6;
    return availableHeight / HOURS.length;
  }, [currentSize, fill, stickyHeaderHeight, viewportHeight]);

  // In fill mode the floor is the auto-fit value itself — once the full 24h
  // is visible, you can't zoom out further (would just add whitespace below).
  // In widget mode allow shrinking down to the absolute MIN.
  const slotHeightFloor = fill
    ? Math.max(MIN_SLOT_HEIGHT, baseSlotHeight)
    : MIN_SLOT_HEIGHT;

  const slotHeight = Math.max(
    slotHeightFloor,
    Math.min(MAX_SLOT_HEIGHT, baseSlotHeight * userZoom)
  );

  const timedGridHeight = HOURS.length * slotHeight;
  const bodyHeight = currentSize === "large" ? 760 : 620;
  const shouldFitWithoutScroll =
    fill &&
    viewportHeight > 0 &&
    stickyHeaderHeight > 0 &&
    timedGridHeight + stickyHeaderHeight <= viewportHeight + 1;

  // Auto-position scrollTop when the week actually changes (or after unmin),
  // but NOT on every slotHeight change — otherwise wheel-zoom would be
  // fought by this effect snapping back to "now". The wheel-zoom path uses
  // its own anchored scroll restore (see useLayoutEffect below).
  const lastPositionedKeyRef = useRef<string | null>(null);
  useEffect(() => {
    if (isMinimized || !scrollRef.current) return;
    // Composite key — re-positions when the week changes, when minimize
    // toggles back on, or when the auto-fit threshold flips.
    const key = `${weekStart.getTime()}|${shouldFitWithoutScroll ? 1 : 0}`;
    if (lastPositionedKeyRef.current === key) return;
    lastPositionedKeyRef.current = key;

    const container = scrollRef.current;
    if (shouldFitWithoutScroll) {
      container.scrollTop = 0;
      return;
    }
    const isCurrentWeek = weekDays.some((day) => isToday(day));
    const targetHour = isCurrentWeek ? new Date().getHours() + new Date().getMinutes() / 60 : 7;
    container.scrollTop = Math.max(0, targetHour * slotHeight - 3 * slotHeight);
  }, [isMinimized, shouldFitWithoutScroll, slotHeight, weekDays, weekStart]);

  // Forget last-positioned key when minimized so re-show triggers a fresh
  // auto-position next render.
  useEffect(() => {
    if (isMinimized) lastPositionedKeyRef.current = null;
  }, [isMinimized]);

  // --- Cursor-anchored vertical wheel zoom ---
  // Ctrl/Cmd + wheel (and Mac trackpad pinch — browsers deliver pinch as
  // ctrl+wheel) zooms slot height continuously, anchored on cursor Y so
  // the hour under the cursor stays put. We attach a native listener to
  // bypass React's passive onWheel and call preventDefault.
  //
  // Two refs handle compounding within a single animation frame:
  //   • desiredSlotHeightRef — the latest in-flight slot height. Wheel
  //     events fire faster than React commits, so reading committed state
  //     would mean each event computes against the same stale value and
  //     only the LAST one wins. Tracking the desired value in a ref makes
  //     each event compound on top of the previous one.
  //   • pendingAnchorRef — captured ONCE per batch (the first wheel event
  //     that has no pending anchor). The original (oldSlotHeight,
  //     oldScrollTop, mouseY) are kept until the layout effect commits so
  //     the cumulative scroll restore is correct.
  const desiredSlotHeightRef = useRef<number | null>(null);
  const pendingAnchorRef = useRef<{
    mouseY: number;
    oldSlotHeight: number;
    oldScrollTop: number;
  } | null>(null);

  const wheelStateRef = useRef({
    slotHeight,
    baseSlotHeight,
    slotHeightFloor,
    stickyHeaderHeight,
  });
  useEffect(() => {
    wheelStateRef.current = { slotHeight, baseSlotHeight, slotHeightFloor, stickyHeaderHeight };
    // Sync the in-flight ref to the committed value (handles external
    // changes like viewport resize that recompute baseSlotHeight).
    desiredSlotHeightRef.current = slotHeight;
  }, [slotHeight, baseSlotHeight, slotHeightFloor, stickyHeaderHeight]);

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

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

      // Normalize line-mode deltas → pixels so deltaMode=1 wheels behave.
      const dy = e.deltaMode === 1 ? e.deltaY * 16 : e.deltaY;
      // Exponential scaling — uniform feel across the zoom range.
      const factor = Math.exp(-dy * 0.008);
      const current = desiredSlotHeightRef.current ?? state.slotHeight;
      const desired = current * factor;
      const clamped = Math.max(state.slotHeightFloor, Math.min(MAX_SLOT_HEIGHT, desired));
      if (Math.abs(clamped - current) < 0.01) return;

      const rect = node.getBoundingClientRect();
      const mouseY = e.clientY - rect.top;
      // Capture original snapshot ONCE per batch — subsequent events in
      // the same frame just refresh the cursor position.
      if (!pendingAnchorRef.current) {
        pendingAnchorRef.current = {
          mouseY,
          oldSlotHeight: current,
          oldScrollTop: node.scrollTop,
        };
      } else {
        pendingAnchorRef.current.mouseY = mouseY;
      }

      desiredSlotHeightRef.current = clamped;
      // Derive userZoom from the desired final slotHeight; the slotHeight
      // computation re-clamps next render so over-shoot is harmless.
      setUserZoom(clamped / state.baseSlotHeight);
    };

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

  // After the new slotHeight has been laid out, restore scroll so the hour
  // under the cursor at zoom-time is still under the cursor now.
  useLayoutEffect(() => {
    const pending = pendingAnchorRef.current;
    if (!pending || !scrollRef.current) return;
    pendingAnchorRef.current = null;

    const { mouseY, oldSlotHeight, oldScrollTop } = pending;
    const ratio = slotHeight / oldSlotHeight;
    // Clamp anchor to the timed-grid region — if the cursor is inside the
    // sticky day-headers, anchor on the top of the timed grid instead.
    const anchorY = Math.max(stickyHeaderHeight, mouseY);
    const newScrollTop =
      (anchorY - stickyHeaderHeight) * (ratio - 1) + oldScrollTop * ratio;
    scrollRef.current.scrollTop = Math.max(0, newScrollTop);
  }, [slotHeight, stickyHeaderHeight]);

  // At very compressed slot heights the hour labels would visually stack on
  // top of each other — show every Nth label instead so the gutter stays
  // legible all the way down to the MIN_SLOT_HEIGHT floor.
  const labelStep = slotHeight < 10 ? 6 : slotHeight < 16 ? 3 : slotHeight < 22 ? 2 : 1;

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

  const goToToday = () => {
    setWeekStart(startOfWeek(startOfToday(), { weekStartsOn: 1 }));
  };

  const currentMinutes = new Date().getHours() * 60 + new Date().getMinutes();
  const nowLineTop = (currentMinutes / 60) * slotHeight;
  const weekLabel = getWeekLabel(weekStart);
  const hasTodayInWeek = weekDays.some((day) => isToday(day));

  return (
    <div
      className={cn(
        "w-full overflow-hidden rounded-2xl border border-gray-100 bg-white",
        fill ? "flex h-full flex-col" : "shadow-sm",
        "focus:outline-none focus:ring-2 focus:ring-blue-500/20"
      )}
      tabIndex={0}
      onKeyDown={(e) => {
        if (e.ctrlKey) {
          if (e.key === 'ArrowLeft') {
            e.preventDefault();
            setWeekStart((current) => addDays(current, -DAYS_IN_WEEK));
          } else if (e.key === 'ArrowRight') {
            e.preventDefault();
            setWeekStart((current) => addDays(current, DAYS_IN_WEEK));
          }
        }
      }}
    >
      {isMinimized ? (
        <button
          type="button"
          onClick={toggleMinimize}
          className="flex w-full items-center justify-between gap-4 px-5 py-3 text-left transition-colors hover:bg-gray-50"
          title="Show timeline"
        >
          <div className="flex items-center gap-3">
            <div className="flex h-10 w-10 items-center justify-center rounded-2xl bg-blue-100 text-blue-700">
              <Calendar size={18} strokeWidth={2} />
            </div>
            <div>
              <p className="text-sm font-semibold text-gray-900">Weekly medical agenda</p>
              <p className="text-xs text-gray-500">{weekLabel}</p>
            </div>
          </div>
          <div className="flex items-center gap-1 text-xs font-semibold text-gray-500">
            Show
            <ChevronDown size={14} strokeWidth={2} />
          </div>
        </button>
      ) : (
        <>
          <div className="border-b border-gray-100 bg-white px-4 py-2.5">
            <div className="flex items-center justify-between gap-3">
              <div className="flex min-w-0 items-center gap-3">
                {!fill && (
                  <button
                    onClick={toggleMinimize}
                    className="btn-icon"
                    title="Hide timeline"
                  >
                    <Calendar size={16} strokeWidth={2} />
                  </button>
                )}

                <p className="text-sm font-semibold text-gray-900 truncate">
                  {weekLabel}
                </p>
              </div>

              <div className="flex items-center gap-2">
                <div className="inline-flex items-center rounded-lg border border-gray-200 bg-gray-100 p-0.5">
                  <button
                    onClick={() => setWeekStart((current) => addDays(current, -DAYS_IN_WEEK))}
                    className="flex h-7 w-7 items-center justify-center rounded-md text-gray-500 transition-colors hover:bg-white hover:text-gray-900"
                    title="Previous week"
                  >
                    <ChevronLeft size={15} strokeWidth={2} />
                  </button>
                  <button
                    onClick={goToToday}
                    className={cn(
                      "rounded-md px-3 py-1 text-xs font-semibold transition-colors",
                      hasTodayInWeek
                        ? "text-primary"
                        : "bg-white text-gray-700 shadow-sm hover:bg-gray-50"
                    )}
                  >
                    Today
                  </button>
                  <button
                    onClick={() => setWeekStart((current) => addDays(current, DAYS_IN_WEEK))}
                    className="flex h-7 w-7 items-center justify-center rounded-md text-gray-500 transition-colors hover:bg-white hover:text-gray-900"
                    title="Next week"
                  >
                    <ChevronRight size={15} strokeWidth={2} />
                  </button>
                </div>

                <div className="flex items-center gap-0.5 rounded-lg border border-gray-200 bg-gray-100 p-0.5">
                  {(
                    Object.keys(EVENT_META) as DisplayedEventType[]
                  ).map((type) => {
                    const meta = EVENT_META[type];
                    const Icon = meta.icon;
                    const isActive =
                      activeFilters.length === 0 || activeFilters.includes(type);
                    return (
                      <button
                        key={type}
                        onClick={() => toggleFilter(type)}
                        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-gray-900 shadow-sm"
                            : "text-gray-500 hover:text-gray-800"
                        )}
                        title={`Toggle ${type}`}
                      >
                        <Icon
                          size={13}
                          strokeWidth={2}
                          className={isActive ? meta.iconClass : "text-gray-400"}
                        />
                        <span className="hidden sm:inline capitalize">{type}</span>
                      </button>
                    );
                  })}
                </div>

                {!fill && (
                  <button
                    onClick={() =>
                      setCurrentSize((value) =>
                        value === "large" ? "default" : "large"
                      )
                    }
                    className="btn-icon"
                    title={currentSize === "large" ? "Compact view" : "Expanded view"}
                  >
                    {currentSize === "large" ? (
                      <Minimize2 size={15} strokeWidth={2} />
                    ) : (
                      <Maximize2 size={15} strokeWidth={2} />
                    )}
                  </button>
                )}
              </div>
            </div>
          </div>

          <div
            ref={viewportRef}
            className={cn(
              "overflow-hidden transition-[height,opacity] duration-300 ease-out",
              fill && "flex-1 min-h-0"
            )}
            style={fill ? undefined : { height: `${bodyHeight}px` }}
          >
            <div
              ref={scrollRef}
              className="h-full overflow-auto bg-white"
            >
              <div
                className="min-w-[980px]"
                style={{
                  gridTemplateColumns: `${TIME_GUTTER_WIDTH}px repeat(${DAYS_IN_WEEK}, minmax(${DAY_COLUMN_MIN_WIDTH}px, 1fr))`,
                }}
              >
                <div ref={stickyHeaderRef} className="sticky top-0 z-30 border-b border-gray-100 bg-white">
                  <div
                    className="grid"
                    style={{
                      gridTemplateColumns: `${TIME_GUTTER_WIDTH}px repeat(${DAYS_IN_WEEK}, minmax(${DAY_COLUMN_MIN_WIDTH}px, 1fr))`,
                    }}
                  >
                    <div className="border-r border-gray-100" />

                    {weekDays.map((day) => (
                      <div
                        key={day.toISOString()}
                        className={cn(
                          "border-r border-gray-100 px-2 py-2 text-center last:border-r-0",
                          isToday(day) && "bg-blue-50/40"
                        )}
                      >
                        <p
                          className={cn(
                            "text-[10px] font-medium uppercase tracking-wider",
                            isToday(day) ? "text-primary" : "text-gray-400"
                          )}
                        >
                          {format(day, "EEE")}
                        </p>
                        <p
                          className={cn(
                            "mt-0.5 text-[18px] font-semibold tabular-nums leading-tight",
                            isToday(day) ? "text-primary" : "text-gray-800"
                          )}
                        >
                          {format(day, "d")}
                        </p>
                      </div>
                    ))}
                  </div>

                  <div
                    className="grid border-t border-gray-100"
                    style={{
                      gridTemplateColumns: `${TIME_GUTTER_WIDTH}px repeat(${DAYS_IN_WEEK}, minmax(${DAY_COLUMN_MIN_WIDTH}px, 1fr))`,
                    }}
                  >
                    <div className="border-r border-gray-100 flex items-center justify-end pr-3">
                      <p className="text-[9px] font-medium uppercase tracking-wider text-gray-400">
                        All day
                      </p>
                    </div>

                    <div className="relative col-span-7 border-r-0" style={{ height: `${allDayHeight}px` }}>
                      <div className="absolute inset-0 grid grid-cols-7">
                        {weekDays.map((day) => (
                          <div
                            key={`all-day-${day.toISOString()}`}
                            className={cn(
                              "border-r border-gray-100 last:border-r-0",
                              isToday(day) && "bg-blue-50/40"
                            )}
                          />
                        ))}
                      </div>

                      {allDayLayouts.length === 0 ? (
                        <div className="absolute inset-0" />
                      ) : (
                        allDayLayouts.map((item) => {
                          const meta = EVENT_META[item.event.type as DisplayedEventType];
                          const Icon = meta.icon;
                          const leftPercent = (item.startIndex / DAYS_IN_WEEK) * 100;
                          const widthPercent =
                            ((item.endIndex - item.startIndex + 1) / DAYS_IN_WEEK) * 100;

                          return (
                            <Popover key={item.event.id}>
                              <PopoverTrigger asChild>
                                <button
                                  className={cn(
                                    "absolute flex items-center justify-center gap-1.5 overflow-hidden rounded-md border px-2 text-center transition-colors",
                                    meta.bar
                                  )}
                                  style={{
                                    left: `calc(${leftPercent}% + 4px)`,
                                    width: `calc(${widthPercent}% - 8px)`,
                                    top: `${3 + item.lane * ALL_DAY_ROW_HEIGHT}px`,
                                    height: `${ALL_DAY_ROW_HEIGHT - 3}px`,
                                  }}
                                >
                                  <Icon
                                    size={11}
                                    strokeWidth={2.25}
                                    className={cn("shrink-0", meta.iconClass)}
                                  />
                                  <span className={cn("block truncate text-[11px] font-medium", meta.iconClass)}>
                                    {getEventTitle(item.event)}
                                  </span>
                                </button>
                              </PopoverTrigger>
                              <PopoverContent side="bottom" align="start" className="w-[340px]">
                                <EventPopoverContent event={item.event as DisplayedTimelineEvent} />
                              </PopoverContent>
                            </Popover>
                          );
                        })
                      )}
                    </div>
                  </div>
                </div>

                <div
                  className="grid"
                  style={{
                    gridTemplateColumns: `${TIME_GUTTER_WIDTH}px repeat(${DAYS_IN_WEEK}, minmax(${DAY_COLUMN_MIN_WIDTH}px, 1fr))`,
                  }}
                >
                  <div className="border-r border-gray-100">
                    {HOURS.map((hour) => (
                      <div
                        key={hour}
                        className="relative"
                        style={{ height: `${slotHeight}px` }}
                      >
                        {hour % labelStep === 0 && (
                          <span className="absolute -top-1.5 right-2 text-[10px] font-medium tabular-nums text-gray-400">
                            {format(new Date().setHours(hour, 0, 0, 0), "HH:mm")}
                          </span>
                        )}
                      </div>
                    ))}
                  </div>

                  {weekDays.map((day, dayIndex) => {
                    const dayEvents = timedLayouts.filter((item) => item.dayIndex === dayIndex);
                    return (
                      <div
                        key={`column-${day.toISOString()}`}
                        className={cn(
                          "relative border-r border-gray-100 last:border-r-0",
                          isToday(day) && "bg-blue-50/30"
                        )}
                        style={{ height: `${timedGridHeight}px` }}
                      >
                        {HOURS.map((hour) => (
                          <div
                            key={`${day.toISOString()}-${hour}`}
                            className="border-b border-gray-100/80"
                            style={{ height: `${slotHeight}px` }}
                          />
                        ))}

                        {hasTodayInWeek && (
                          <>
                            <div
                              className="pointer-events-none absolute left-0 right-0 z-10 h-px bg-primary/70"
                              style={{ top: `${nowLineTop}px` }}
                            />
                            {isToday(day) && (
                              <span
                                className="pointer-events-none absolute -left-[3px] z-10 h-1.5 w-1.5 rounded-full bg-primary"
                                style={{ top: `${nowLineTop - 3}px` }}
                              />
                            )}
                          </>
                        )}

                        {dayEvents.map((item) => {
                          const meta = EVENT_META[item.event.type];
                          const Icon = meta.icon;
                          const blockHeight = Math.max(
                            ((item.endMinutes - item.startMinutes) / 60) * slotHeight,
                            fill ? 24 : currentSize === "large" ? 56 : 46
                          );
                          const isCompact = blockHeight < 38;
                          const top = (item.startMinutes / 60) * slotHeight + 1;
                          const left = `calc(${(item.lane / item.laneCount) * 100}% + ${
                            item.lane * BLOCK_GAP + 2
                          }px)`;
                          const width = `calc(${100 / item.laneCount}% - ${
                            ((item.laneCount - 1) * BLOCK_GAP) / item.laneCount + 4
                          }px)`;

                          return (
                            <Popover key={item.event.id}>
                              <PopoverTrigger asChild>
                                <button
                                  className={cn(
                                    "absolute flex items-center justify-center overflow-hidden rounded-md border px-2 text-center transition-colors",
                                    meta.bar
                                  )}
                                  style={{
                                    top: `${top}px`,
                                    left,
                                    width,
                                    height: `${blockHeight}px`,
                                  }}
                                >
                                  <div className="flex min-w-0 items-center justify-center gap-1.5">
                                    <Icon
                                      size={isCompact ? 11 : 12}
                                      strokeWidth={2.25}
                                      className={cn("shrink-0", meta.iconClass)}
                                    />
                                    <div className="min-w-0">
                                      <p
                                        className={cn(
                                          "truncate font-medium text-gray-900",
                                          isCompact ? "text-[11px] leading-tight" : "text-xs leading-tight"
                                        )}
                                      >
                                        {getEventTitle(item.event)}
                                      </p>
                                      {!isCompact && blockHeight >= 48 && (
                                        <p className="mt-0.5 text-[10px] tabular-nums text-gray-500">
                                          {formatMinutesLabel(item.startMinutes)}
                                        </p>
                                      )}
                                    </div>
                                  </div>
                                </button>
                              </PopoverTrigger>
                              <PopoverContent side="top" align="start" className="w-[340px]">
                                <EventPopoverContent event={item.event as DisplayedTimelineEvent} />
                              </PopoverContent>
                            </Popover>
                          );
                        })}
                      </div>
                    );
                  })}
                </div>

                {!hasVisibleEvents && (
                  <div className="border-t border-gray-100 px-6 py-10 text-center">
                    <p className="text-sm font-semibold text-gray-900">
                      Nothing matches this week
                    </p>
                    <p className="mt-1 text-xs text-gray-500">
                      Try another week or re-enable one of the event filters.
                    </p>
                  </div>
                )}
              </div>
            </div>
          </div>
        </>
      )}
    </div>
  );
}
