"use client";

import { EventPopover } from "@/components/calendar/EventPopover";
import { Skeleton } from "@/components/ui/skeleton";
import { wpToLuxonFormat } from "@/lib/date-utils";
import { formatDate } from "@fullcalendar/core";
import allLocales from "@fullcalendar/core/locales-all";
import dayGridPlugin from "@fullcalendar/daygrid";
import listPlugin from "@fullcalendar/list";
import luxonPlugin from "@fullcalendar/luxon3";
import FullCalendar from "@fullcalendar/react";
import timeGridPlugin from "@fullcalendar/timegrid";
import { __, sprintf } from "@wordpress/i18n";
import { DateTime } from "luxon";
import { useEffect, useRef } from "react";

const days = {
  sunday: 0,
  monday: 1,
  tuesday: 2,
  wednesday: 3,
  thursday: 4,
  friday: 5,
  saturday: 6,
};

const wpLocale =
  typeof window !== "undefined" && window.eventkoi_params
    ? window.eventkoi_params.locale
    : "en";

// Convert "de_DE" → "de"
const shortLocale = (wpLocale || "").split("_")[0] || "en";

// Verify if locale exists in FullCalendar bundle
const supported = allLocales.some((l) => l.code === shortLocale);
let localeToUse = supported ? shortLocale : "en";

// Final safety check: ensure browser Intl accepts it
try {
  new Intl.DateTimeFormat(localeToUse);
} catch {
  localeToUse = "en";
}

const getDisplayTimezoneForUrl = (timezone, calendarTimeZone) => {
  if (timezone === "local") {
    return (
      Intl.DateTimeFormat().resolvedOptions().timeZone ||
      calendarTimeZone ||
      "UTC"
    );
  }

  return calendarTimeZone || timezone || window.eventkoi_params?.timezone || "UTC";
};

const getEventUrlWithTimezone = (url, timezone, calendarTimeZone) => {
  if (!url) {
    return url;
  }

  try {
    const nextUrl = new URL(url, window.location.href);
    nextUrl.searchParams.set(
      "tz",
      getDisplayTimezoneForUrl(timezone, calendarTimeZone)
    );
    return nextUrl.toString();
  } catch {
    return url;
  }
};

// Google-Calendar-style cascade for overlapping events in week/day view.
// FullCalendar's default splits overlappers into equal-width side-by-side
// columns. We instead let each subsequent overlapper take a generous left
// indent (so the underlying event's left half stays readable) while still
// extending to the column's right edge. With rounded corners + shadow on
// the overlapper, the stack reads as separate floating cards.
const cascadeTimegridColumn = (col) => {
  const harnesses = Array.from(
    col.querySelectorAll(":scope > .fc-timegrid-event-harness")
  );
  if (harnesses.length < 2) {
    harnesses.forEach((h) => {
      h.style.insetInlineStart = "0";
      h.style.insetInlineEnd = "0";
      h.style.zIndex = "1";
      h.classList.remove("ek-cascade-overlap");
    });
    return;
  }

  const colWidth = col.getBoundingClientRect().width || 200;

  const rects = harnesses.map((el) => {
    const top = parseFloat(el.style.top) || 0;
    const bottomCss = parseFloat(el.style.bottom);
    const heightCss = parseFloat(el.style.height);
    let bottom;
    if (Number.isFinite(bottomCss)) {
      const parentH = el.parentElement?.getBoundingClientRect().height || 0;
      bottom = parentH - bottomCss;
    } else if (Number.isFinite(heightCss)) {
      bottom = top + heightCss;
    } else {
      const r = el.getBoundingClientRect();
      bottom = top + r.height;
    }
    return { el, top, bottom };
  });

  rects.sort((a, b) => a.top - b.top || a.bottom - b.bottom);

  const active = [];
  let maxLevel = 0;
  rects.forEach((r) => {
    for (let i = active.length - 1; i >= 0; i--) {
      if (active[i].bottom <= r.top + 0.5) active.splice(i, 1);
    }
    const used = new Set(active.map((a) => a.level));
    let level = 0;
    while (used.has(level)) level++;
    r.level = level;
    if (level > maxLevel) maxLevel = level;
    active.push({ bottom: r.bottom, level });
  });

  // Scale the per-level indent to the cascade depth so the deepest card
  // still has at least MIN_VISIBLE width. For a 2-event stack this falls
  // out to ~45% of the column; deeper stacks use a smaller step so the
  // bottom card isn't crushed.
  const MIN_VISIBLE = 56;
  const idealForDepth =
    maxLevel > 0 ? (colWidth - MIN_VISIBLE) / maxLevel : colWidth * 0.45;
  const gutter = Math.max(
    20,
    Math.min(idealForDepth, colWidth * 0.45, 140)
  );

  rects.forEach((r) => {
    r.el.style.insetInlineStart = `${r.level * gutter}px`;
    r.el.style.insetInlineEnd = "0";
    r.el.style.zIndex = String(r.level + 1);
    if (r.level > 0) {
      r.el.classList.add("ek-cascade-overlap");
    } else {
      r.el.classList.remove("ek-cascade-overlap");
    }
  });
};

const cascadeTimegrid = (root) => {
  const cols = (root || document).querySelectorAll(".fc-timegrid-col-events");
  cols.forEach(cascadeTimegridColumn);
};

export function CalendarGridMode({
  calendarRef,
  events,
  view,
  timezone,
  setCurrentDate,
  lastRangeRef,
  loadEventsForView,
  selectedEvent,
  setSelectedEvent,
  anchorPos,
  setAnchorPos,
  ignoreNextOutsideClick,
  calendar,
  isEmpty,
  eventColor,
  timeFormat,
  startday,
  initialDate,
}) {
  const resolvedLocalTz =
    typeof window !== "undefined"
      ? Intl.DateTimeFormat().resolvedOptions().timeZone
      : null;
  const calendarTimeZone =
    timezone === "local"
      ? resolvedLocalTz || "UTC"
      : timezone && timezone !== "local"
      ? timezone
      : null;
  const displayTimezoneKey = calendarTimeZone || timezone || "UTC";
  const previousTimezoneRef = useRef(displayTimezoneKey);
  const cascadeScheduledRef = useRef(false);
  const scheduleCascade = () => {
    if (cascadeScheduledRef.current) return;
    cascadeScheduledRef.current = true;
    requestAnimationFrame(() => {
      cascadeScheduledRef.current = false;
      const root = calendarRef?.current?.elRef?.current || document;
      cascadeTimegrid(root);
    });
  };
  const resolvedWpTimeFormat =
    (typeof eventkoi_params !== "undefined" &&
      window.eventkoi_params?.time_format_string) ||
    (timeFormat === "24" ? "H:i" : "g:i a");
  const formatWpTime = (dt) => {
    if (!dt?.isValid) {
      return "";
    }

    let formatted = dt
      .setLocale(localeToUse)
      .toFormat(wpToLuxonFormat(resolvedWpTimeFormat));

    if (resolvedWpTimeFormat.includes("A")) {
      formatted = formatted.replace(/\b(am|pm)\b/g, (match) =>
        match.toUpperCase()
      );
    } else if (resolvedWpTimeFormat.includes("a")) {
      formatted = formatted.replace(/\b(AM|PM)\b/g, (match) =>
        match.toLowerCase()
      );
    }

    return formatted;
  };
  const formatCalendarTime = (date, dateStr) => {
    let dt = dateStr ? DateTime.fromISO(dateStr, { setZone: true }) : null;

    if (!dt?.isValid && date instanceof Date) {
      dt = DateTime.fromJSDate(date, { zone: calendarTimeZone || "UTC" });
    }

    if (!dt?.isValid) {
      return "";
    }

    if (calendarTimeZone) {
      dt = dt.setZone(calendarTimeZone);
    }

    return formatWpTime(dt);
  };
  const shouldShowEventEndTime = (arg) => {
    return (
      arg.view?.type?.startsWith("timeGrid") &&
      arg.event?.start instanceof Date &&
      arg.event?.end instanceof Date &&
      arg.event.end > arg.event.start
    );
  };
  const getCalendarDateKey = (date, dateStr) => {
    let dt = dateStr ? DateTime.fromISO(dateStr, { setZone: true }) : null;
    if (!dt?.isValid && date instanceof Date) {
      dt = DateTime.fromJSDate(date, { zone: calendarTimeZone || "UTC" });
    }
    if (!dt?.isValid) {
      return "";
    }
    return (calendarTimeZone ? dt.setZone(calendarTimeZone) : dt).toISODate();
  };
  const isTimedMultiDayEvent = (arg) => {
    if (!shouldShowEventEndTime(arg)) {
      return false;
    }
    const startKey = getCalendarDateKey(arg.event.start, arg.event.startStr);
    const endKey = getCalendarDateKey(arg.event.end, arg.event.endStr);
    return !!startKey && !!endKey && startKey !== endKey;
  };
  const formatCalendarEndDay = (date, dateStr) => {
    let dt = dateStr ? DateTime.fromISO(dateStr, { setZone: true }) : null;
    if (!dt?.isValid && date instanceof Date) {
      dt = DateTime.fromJSDate(date, { zone: calendarTimeZone || "UTC" });
    }
    if (!dt?.isValid) {
      return "";
    }
    if (calendarTimeZone) {
      dt = dt.setZone(calendarTimeZone);
    }
    return dt.setLocale(localeToUse).toFormat("ccc");
  };
  const toCalendarDateTime = (date, dateStr) => {
    let dt = dateStr ? DateTime.fromISO(dateStr, { setZone: true }) : null;
    if (!dt?.isValid && date instanceof Date) {
      dt = DateTime.fromJSDate(date, { zone: calendarTimeZone || "UTC" });
    }
    if (!dt?.isValid) {
      return null;
    }
    return calendarTimeZone ? dt.setZone(calendarTimeZone) : dt;
  };
  // Google-style compact time formatter for week-view tile labels.
  // 12h: "9 – 11am", "1:30 – 3pm", "11am – 2pm" (drops :00, drops first
  // period when both endpoints share one). 24h must never emit a bare hour:
  // "15" has nothing marking it as a time, so on French sites it read as a
  // count of events instead of 3pm. French gets its own convention ("15h",
  // "18h30"); other 24h locales keep the minutes ("15:00", "18:30").
  const isFrenchLocale = /^fr/i.test(localeToUse);
  const formatCompactTime = (dt) => {
    if (!dt) return "";
    const m = dt.minute;
    const mm = String(m).padStart(2, "0");
    if (timeFormat === "24") {
      if (isFrenchLocale) {
        return `${dt.hour}h${m === 0 ? "" : mm}`;
      }
      return `${dt.hour}:${mm}`;
    }
    const h12 = ((dt.hour + 11) % 12) + 1;
    return `${h12}${m === 0 ? "" : `:${mm}`}`;
  };
  const formatCompactRange = (start, end) => {
    const sStr = formatCompactTime(start);
    const eStr = formatCompactTime(end);
    if (timeFormat === "24") {
      return `${sStr} – ${eStr}`;
    }
    const sP = start.hour < 12 ? "am" : "pm";
    const eP = end.hour < 12 ? "am" : "pm";
    if (sP === eP) {
      return `${sStr} – ${eStr}${eP}`;
    }
    return `${sStr}${sP} – ${eStr}${eP}`;
  };
  const formatCompactStart = (dt) => {
    const core = formatCompactTime(dt);
    if (timeFormat === "24") return core;
    return `${core}${dt.hour < 12 ? "am" : "pm"}`;
  };
  const formatCalendarTimeRange = (arg) => {
    const startDt = toCalendarDateTime(arg.event.start, arg.event.startStr);
    if (!startDt) {
      return "";
    }

    const isDayGrid = arg.view?.type?.startsWith("dayGrid");

    if (!shouldShowEventEndTime(arg)) {
      // Compact start time in month/list dot events so narrow cells
      // don't truncate the title.
      if (isDayGrid) {
        return formatCompactStart(startDt);
      }
      return formatCalendarTime(arg.event.start, arg.event.startStr);
    }

    const endDt = toCalendarDateTime(arg.event.end, arg.event.endStr);
    if (!endDt) {
      return formatCalendarTime(arg.event.start, arg.event.startStr);
    }

    // Google-style multi-day display: start segment shows the full range
    // including the end weekday ("3am – Sun 8pm"). Middle segments carry a
    // "Full day" label so all-day-height spans do not look like missing data.
    if (isTimedMultiDayEvent(arg)) {
      const isStartSegment = arg.isStart !== false;
      const isEndSegment = arg.isEnd !== false;
      if (!isStartSegment) {
        if (isEndSegment) {
          return sprintf(
            /* translators: %s: event end time. */
            __("Until %s", "eventkoi-lite"),
            formatCompactStart(endDt)
          );
        }

        return __("Full day", "eventkoi-lite");
      }
      const endDay = formatCalendarEndDay(arg.event.end, arg.event.endStr);
      const sCompact = formatCompactTime(startDt);
      const eCompact = formatCompactTime(endDt);
      const sP = timeFormat === "24" ? "" : startDt.hour < 12 ? "am" : "pm";
      const eP = timeFormat === "24" ? "" : endDt.hour < 12 ? "am" : "pm";
      const startPart = `${sCompact}${sP}`;
      const endPart = `${eCompact}${eP}`;
      return endDay
        ? `${startPart} – ${endDay} ${endPart}`
        : `${startPart} – ${endPart}`;
    }

    return formatCompactRange(startDt, endDt);
  };
  const renderEventContent = (arg) => {
    const title = arg.event?.title || "";
    // Only emit the colored dot in dayGrid (month) view where events
    // render as "dot + text" on a white background. In timeGrid the
    // whole event is already filled with the calendar's color, so a
    // dot is redundant and just eats horizontal space.
    const isTimeGrid = arg.view?.type?.startsWith("timeGrid");
    const dotColor =
      arg.event?.borderColor ||
      arg.event?.backgroundColor ||
      arg.borderColor ||
      arg.backgroundColor;
    const dot =
      !isTimeGrid && dotColor ? (
        <div
          className="fc-daygrid-event-dot"
          style={{ borderColor: dotColor }}
        />
      ) : null;

    if (arg.event?.allDay || !arg.event?.start) {
      return (
        <>
          {dot}
          <span className="fc-event-title">{title}</span>
        </>
      );
    }

    // Empty return from formatCalendarTimeRange is intentional (multi-day
    // middle/end segment, Google style). Don't fall through to FC's
    // default which would resurrect the hidden time.
    const timeText = formatCalendarTimeRange(arg);

    return (
      <>
        {dot}
        {timeText ? <span className="fc-event-time">{timeText}</span> : null}
        {timeText ? " " : null}
        <span className="fc-event-title">{title}</span>
      </>
    );
  };
  const formatInCalendarTz = (date, options) => {
    const opts = calendarTimeZone
      ? { ...options, timeZone: calendarTimeZone }
      : options;

    try {
      return new Intl.DateTimeFormat(localeToUse, opts).format(date);
    } catch {
      return new Intl.DateTimeFormat(localeToUse, options).format(date);
    }
  };

  const eventTimeFormat =
    timeFormat === "24"
      ? {
          hour: "2-digit",
          minute: "2-digit",
          hour12: false,
        }
      : {
          hour: "numeric",
          minute: "2-digit",
          hour12: true,
          // Keep the am/pm suffix in every locale: without it, non-English
          // sites rendered a bare hour ("10"), which reads as a count.
          omitZeroMinute: true,
          meridiem: "short",
        };

  const formatSlotLabel = (date) => {
    if (timeFormat === "24") {
      return formatInCalendarTz(date, {
        hour: "2-digit",
        minute: "2-digit",
        hour12: false,
      });
    }

    const formatted = formatInCalendarTz(date, {
      hour: "numeric",
      minute: "2-digit",
      hour12: true,
    });
    const withoutZeroMinutes = formatted.replace(/:00\b/, "");

    // Normalize or restore the am/pm suffix for every locale so a bare hour
    // is never shown (a French "10" is indistinguishable from a count).
    if (/([ap])\.?m?\.?$/i.test(withoutZeroMinutes)) {
      return withoutZeroMinutes.replace(/\s?([ap])\.?m?\.?$/i, (match, part) => {
        return ` ${part.toUpperCase()}M`;
      });
    }

    const numeric = withoutZeroMinutes.match(/\d{1,2}/);
    if (!numeric) {
      return withoutZeroMinutes;
    }

    const hour24 = parseInt(
      formatInCalendarTz(date, { hour: "numeric", hour12: false }),
      10
    );
    if (Number.isNaN(hour24)) {
      return withoutZeroMinutes;
    }

    return `${withoutZeroMinutes} ${hour24 < 12 ? "AM" : "PM"}`;
  };

  const normalizeTimeValue = (value) => {
    if (!value) return null;
    if (/^\d{2}:\d{2}:\d{2}$/.test(value)) {
      return value;
    }
    if (/^\d{2}:\d{2}$/.test(value)) {
      return `${value}:00`;
    }
    return null;
  };

  const globalDayStart = window.eventkoi_params?.day_start_time || "00:00";
  const dayStartTime = calendar?.day_start_time || globalDayStart;
  const scrollTime = normalizeTimeValue(dayStartTime) || "07:00:00";
  const slotMinTime = "00:00:00";
  const slotMaxTime = "24:00:00";
  const isTimeGridView =
    typeof view === "string" && view.startsWith("timeGrid");
  const startHour = parseInt(scrollTime.slice(0, 2), 10);
  const endHour = 23; // 11pm
  const visibleHours = Number.isFinite(startHour)
    ? Math.max(1, endHour - startHour + 1)
    : 17; // fallback 7 AM → 11 PM
  const slotMinutes = 30;
  const slotHeightMap = {
    1: 23.9,
    2: 24.1,
    3: 24.2,
    4: 24.4,
    5: 24.6,
    6: 24.8,
    7: 25.0,
    8: 25.2,
    9: 25.5,
    10: 25.8,
    11: 26.2,
    12: 26.6,
    13: 27.2,
    14: 27.8,
    15: 28.6,
    16: 29.4,
    17: 30.7,
    18: 32.4,
    19: 34.6,
    20: 38,
    21: 43,
    22: 54,
    23: 89,
  };
  const slotHeightPx =
    Number.isFinite(startHour) && slotHeightMap[startHour]
      ? slotHeightMap[startHour]
      : 25;
  const slotsToShow = visibleHours * (60 / slotMinutes); // 28 for 9am
  const useDefaultTimeGrid =
    !Number.isFinite(startHour) || startHour === 0;
  const timeGridHeight = useDefaultTimeGrid
    ? "auto"
    : slotsToShow * slotHeightPx;

  useEffect(() => {
    if (previousTimezoneRef.current === displayTimezoneKey) {
      return;
    }

    previousTimezoneRef.current = displayTimezoneKey;

    const api = calendarRef?.current?.getApi?.();
    const activeStart = api?.view?.activeStart;
    const activeEnd = api?.view?.activeEnd;
    const viewType = api?.view?.type || view || "";

    if (!activeStart || !activeEnd) {
      return;
    }

    const key = `${activeStart.toISOString()}_${activeEnd.toISOString()}_${viewType}_${displayTimezoneKey}`;
    const alreadyLoaded = lastRangeRef.current === key;

    setSelectedEvent(null);
    setAnchorPos(null);
    lastRangeRef.current = key;
    setCurrentDate(api.view?.currentStart || api.getDate?.());

    if (!alreadyLoaded) {
      loadEventsForView(activeStart, activeEnd, viewType);
    }

    if (viewType.startsWith("timeGrid")) {
      setTimeout(() => {
        api?.scrollToTime?.(scrollTime);
      }, 0);
    }
  }, [
    calendarRef,
    displayTimezoneKey,
    lastRangeRef,
    loadEventsForView,
    scrollTime,
    setAnchorPos,
    setCurrentDate,
    setSelectedEvent,
    view,
  ]);

  if (isEmpty) {
    return (
      <div className="w-full">
        <div className="grid grid-cols-7 gap-2">
          {Array.from({ length: 35 }).map((_, i) => (
            <Skeleton key={i} className="h-24 w-full rounded-md" />
          ))}
        </div>
      </div>
    );
  }

  // Apply per-event calendar color
  const coloredEvents = Array.isArray(events)
    ? events.map((ev) => ({
        ...ev,
        url: getEventUrlWithTimezone(ev.url, timezone, calendarTimeZone),
        color: ev.calendar_color,
        borderColor: ev.calendar_color,
      }))
    : [];
  // Leave undefined when no concrete color is known so the server-printed
  // `:root` accent wins instead of being overridden by a hardcoded fallback.
  const calendarAccent = eventColor || calendar?.color || null;

  let start_day = days[startday || calendar?.startday || "sunday"];

  const openEventPopover = (eventEl, eventData, jsEvent) => {
    const rect = eventEl.getBoundingClientRect();
    const containerRect = document.querySelector(".fc")?.getBoundingClientRect();
    if (!containerRect) return;

    const popoverWidth = 370;
    // Multi-day harnesses can span hundreds of pixels of column height, so
    // anchoring to rect.bottom drops the popover far below the click. Anchor
    // below the click point when we have it, capped to the harness bottom.
    const clickY = jsEvent?.clientY;
    const anchorY =
      typeof clickY === "number"
        ? Math.min(clickY + 14, rect.bottom + 6)
        : rect.bottom + 6;
    const relY = anchorY - containerRect.top;

    let relX;
    if (rect.right - containerRect.left + popoverWidth > containerRect.width) {
      relX = rect.right - containerRect.left - popoverWidth;
    } else {
      relX = rect.left - containerRect.left;
    }

    if (window.innerWidth < 768) {
      setAnchorPos({ x: 0, y: relY });
    } else {
      setAnchorPos({ x: Math.max(0, relX), y: relY });
    }

    setSelectedEvent({
      ...eventData.extendedProps,
      title: eventData.title,
      start: eventData.startStr,
      end: eventData.endStr,
      allDay: eventData.allDay,
      url: eventData.url,
    });
  };
  const closeEventPopover = () => {
    setSelectedEvent(null);
    setAnchorPos(null);
  };

  return (
    <>
      <div
        className="relative"
        style={calendarAccent ? { "--ek-calendar-accent": calendarAccent } : undefined}
      >
      <FullCalendar
        ref={calendarRef}
        locales={allLocales}
        locale={localeToUse}
        plugins={[dayGridPlugin, timeGridPlugin, listPlugin, luxonPlugin]}
        events={coloredEvents}
        timeZone={calendarTimeZone || "UTC"}
        initialView={view}
        initialDate={initialDate}
        weekends={true}
        firstDay={start_day}
        headerToolbar={false}
        slotEventOverlap={true}
        nowIndicator={true}
        dayCellClassNames={(arg) => {
          // Tint days NOT in the user's configured working_days
          // (eventkoi_settings.working_days, Mon-first 0..6). Falls
          // back to Sat/Sun when the setting is unavailable.
          const wd = window.eventkoi_params?.working_days;
          const jsDay = arg.date.getDay();
          const monFirst = (jsDay + 6) % 7;
          const isWorking = Array.isArray(wd)
            ? wd.map((n) => Number(n)).includes(monFirst)
            : monFirst < 5;
          return isWorking ? "" : "ek-non-working-day";
        }}
        eventsSet={() => scheduleCascade()}
        windowResize={() => scheduleCascade()}
        contentHeight={isTimeGridView ? timeGridHeight : "auto"}
        expandRows={!isTimeGridView}
        height={isTimeGridView ? timeGridHeight : "auto"}
        slotMinTime={slotMinTime}
        slotMaxTime={slotMaxTime}
        scrollTime={scrollTime}
        eventTimeFormat={eventTimeFormat}
        eventContent={renderEventContent}
        slotLabelContent={(args) => {
          const label = formatSlotLabel(args.date);
          return label ? <span>{label}</span> : null;
        }}
        dayHeaderContent={(args) => {
          const { date, view } = args;
          const headerTz = view?.calendar?.getOption("timeZone") || "UTC";
          const formatHeaderDate = (value, options) => {
            if (view?.type?.startsWith("timeGrid") && view?.calendar?.formatDate) {
              return view.calendar.formatDate(value, {
                ...options,
                locale: localeToUse,
              });
            }

            return formatDate(value, {
              ...options,
              locale: localeToUse,
              timeZone: headerTz,
            });
          };

          const dayName = formatHeaderDate(date, {
            weekday: "short",
          });

          // For week/day views → two lines: weekday + bold number
          if (view.type.startsWith("timeGrid")) {
            const dayNum = formatHeaderDate(date, {
              day: "numeric",
            });
            const dayKey = formatHeaderDate(date, {
              year: "numeric",
              month: "2-digit",
              day: "2-digit",
            });
            const todayKey = formatHeaderDate(new Date(), {
              year: "numeric",
              month: "2-digit",
              day: "2-digit",
            });
            const isToday = dayKey === todayKey;
            return (
              <div className="flex flex-col items-center leading-tight">
                <span className={isToday ? "font-semibold" : undefined}>
                  {dayName}
                </span>
                <span
                  className={
                    isToday
                      ? "mt-0.5 inline-flex min-h-[2rem] min-w-[2rem] items-center justify-center rounded-full bg-muted px-2 text-base font-semibold"
                      : "mt-0.5 inline-flex min-h-[2rem] min-w-[2rem] items-center justify-center rounded-full bg-transparent px-2 text-base font-semibold"
                  }
                >
                  {dayNum}
                </span>
              </div>
            );
          }

          // For month or list views → just the weekday name
          return <span>{dayName}</span>;
        }}
        datesSet={({ start, end, view }) => {
          const key = `${start.toISOString()}_${end.toISOString()}_${
            view?.type || ""
          }_${displayTimezoneKey}`;
          if (lastRangeRef.current === key) return;
          closeEventPopover();
          lastRangeRef.current = key;
          const anchorDate =
            calendarRef?.current?.getApi?.()?.getDate?.() ||
            view.currentStart ||
            start;
          loadEventsForView(start, end, view.type, anchorDate);
          setCurrentDate(anchorDate);
          if (view.type.startsWith("timeGrid")) {
            setTimeout(() => {
              const api = calendarRef?.current?.getApi?.();
              api?.scrollToTime?.(scrollTime);
            }, 0);
          }
        }}
        eventClick={(info) => {
          info.jsEvent.preventDefault();
          info.jsEvent.stopPropagation();
          openEventPopover(info.el, info.event, info.jsEvent);
        }}
        eventDidMount={(info) => {
          info.el.setAttribute("aria-label", `${info.event.title}`);
          if (info.view?.type?.startsWith("timeGrid")) {
            scheduleCascade();
          }
        }}
      />
      </div>

      {selectedEvent && anchorPos && (
        <EventPopover
          event={selectedEvent}
          anchor={anchorPos}
          onClose={closeEventPopover}
          ignoreNextOutsideClick={ignoreNextOutsideClick}
          timezone={timezone}
        />
      )}
    </>
  );
}
