import {
  useEffect,
  useMemo,
  useRef,
  useState,
  type CSSProperties,
  type DragEvent as ReactDragEvent,
  type FormEvent,
  type WheelEvent as ReactWheelEvent
} from "react";
import { CalendarDays, ChevronLeft, ChevronRight, Pencil, Plus, RefreshCw, Trash2 } from "lucide-react";
import {
  Button,
  Dialog,
  DismissibleAlert,
  LoadingIndicator,
  ToggleSwitch,
  hasScope,
  type ApiSettings,
  type AuthInfo
} from "@govoplan/core-webui";
import {
  createCalendar,
  createCalendarEvent,
  deleteCalendar,
  deleteCalendarEvent,
  listCalendarEvents,
  listCalendars,
  updateCalendar,
  updateCalendarEvent,
  type CalendarCollection,
  type CalendarCollectionDeletePayload,
  type CalendarDeleteEventAction,
  type CalendarEvent,
  type CalendarEventCreatePayload
} from "../../api/calendar";

type CalendarMode = "continuous" | "month" | "week" | "workweek" | "day";
type Range = { start: Date; end: Date };
type EventDialogState = { kind: "create" } | { kind: "edit"; event: CalendarEvent };
type CalendarEditDialogState = {
  calendar: CalendarCollection;
  eventCount: number | null;
  loadingEventCount: boolean;
  confirmingDelete: boolean;
};
type AgendaGroup = { key: string; date: Date; events: CalendarEvent[] };
type ContinuousViewport = { scrollTop: number; height: number };
type CalendarDayDropTarget = { kind: "day"; key: string };
type CalendarTimeDropTarget = { kind: "time"; key: string; minuteOfDay: number };
type CalendarDropTarget = CalendarDayDropTarget | CalendarTimeDropTarget | null;
type CalendarDragAction =
  | { kind: "move"; event: CalendarEvent }
  | { kind: "resize-start"; event: CalendarEvent }
  | { kind: "resize-end"; event: CalendarEvent };
type CalendarResizeEdge = "start" | "end";
type CalendarViewPreferences = {
  dimWeekends: boolean;
  dimOffHours: boolean;
  workdayStartHour: number;
  workdayEndHour: number;
  continuousVirtualization: boolean;
  continuousOverscanWeeks: number;
  alternateContinuousMonths: boolean;
};

const HOUR_ROW_HEIGHT = 64;
const INITIAL_SCROLL_HOUR = 7;
const MAX_TIMED_EVENT_COLUMNS = 3;
const CONTINUOUS_WEEK_ROW_HEIGHT = 92;
const DEFAULT_CALENDAR_COLOR = "#5aa99b";
const CALENDAR_MODE_STORAGE_KEY = "govoplan.calendar.mode";
const CALENDAR_VIEW_PREFERENCES_STORAGE_KEY = "govoplan.calendar.viewPreferences";
const DEFAULT_CALENDAR_VIEW_PREFERENCES: CalendarViewPreferences = {
  dimWeekends: true,
  dimOffHours: true,
  workdayStartHour: 6,
  workdayEndHour: 20,
  continuousVirtualization: true,
  continuousOverscanWeeks: 6,
  alternateContinuousMonths: true
};

const modeOptions: { id: CalendarMode; label: string }[] = [
  { id: "continuous", label: "Continuous" },
  { id: "month", label: "Month" },
  { id: "week", label: "Week" },
  { id: "workweek", label: "Workweek" },
  { id: "day", label: "Day" }
];

export default function CalendarPage({ settings, auth }: { settings: ApiSettings; auth: AuthInfo }) {
  const [calendars, setCalendars] = useState<CalendarCollection[]>([]);
  const [visibleCalendarIds, setVisibleCalendarIds] = useState<string[]>([]);
  const [eventCalendarId, setEventCalendarId] = useState("");
  const [newCalendarName, setNewCalendarName] = useState("");
  const [newCalendarColor, setNewCalendarColor] = useState(DEFAULT_CALENDAR_COLOR);
  const [events, setEvents] = useState<CalendarEvent[]>([]);
  const [mode, setMode] = useState<CalendarMode>(() => loadCalendarMode());
  const [focusDate, setFocusDate] = useState(() => startOfDay(new Date()));
  const [continuousWeeks, setContinuousWeeks] = useState({ before: 8, after: 12 });
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState("");
  const [eventDialog, setEventDialog] = useState<EventDialogState | null>(null);
  const [calendarEditDialog, setCalendarEditDialog] = useState<CalendarEditDialogState | null>(null);
  const [continuousViewport, setContinuousViewport] = useState<ContinuousViewport>({ scrollTop: 0, height: 0 });
  const [draggingEventId, setDraggingEventId] = useState("");
  const [hoveredEventId, setHoveredEventId] = useState("");
  const [dropTarget, setDropTarget] = useState<CalendarDropTarget>(null);
  const scrollRef = useRef<HTMLDivElement | null>(null);
  const continuousPrependPendingRef = useRef(false);
  const continuousAppendPendingRef = useRef(false);
  const pendingContinuousScrollDayRef = useRef<Date | null>(null);
  const dragActionRef = useRef<CalendarDragAction | null>(null);

  const viewPreferences = useMemo(() => loadCalendarViewPreferences(), []);
  const visibleCalendarIdSet = useMemo(() => new Set(visibleCalendarIds), [visibleCalendarIds]);
  const visibleEvents = useMemo(() => events.filter((event) => visibleCalendarIdSet.has(event.calendar_id)), [events, visibleCalendarIdSet]);
  const calendarColorById = useMemo(() => new Map(calendars.map((calendar) => [calendar.id, calendar.color || DEFAULT_CALENDAR_COLOR])), [calendars]);
  const targetCalendarId = eventCalendarId || calendars[0]?.id || "";
  const visibleRange = useMemo(() => rangeForMode(mode, focusDate, continuousWeeks), [continuousWeeks, focusDate, mode]);
  const days = useMemo(() => daysForMode(mode, focusDate, continuousWeeks), [continuousWeeks, focusDate, mode]);
  const eventsByDay = useMemo(() => groupEventsByDay(visibleEvents), [visibleEvents]);
  const agendaGroups = useMemo(() => agendaGroupsForDays(days, eventsByDay), [days, eventsByDay]);
  const canWrite = hasScope(auth, "calendar:event:write");
  const canDelete = canWrite || hasScope(auth, "calendar:event:delete");
  const canManageCalendars = hasScope(auth, "calendar:calendar:write");
  const canDeleteCalendars = hasScope(auth, "calendar:calendar:admin");
  const heading = headingForMode(mode, focusDate, visibleRange);

  useEffect(() => {
    void loadCalendars();
  }, [settings.apiBaseUrl, settings.apiKey, settings.accessToken]);

  useEffect(() => {
    saveCalendarMode(mode);
  }, [mode]);

  useEffect(() => {
    if (calendars.length) {
      void loadEvents();
    } else {
      setEvents([]);
    }
  }, [calendars.length, visibleRange.start.getTime(), visibleRange.end.getTime()]);

  useEffect(() => {
    if (mode !== "continuous") return undefined;
    const frame = window.requestAnimationFrame(() => {
      const node = scrollRef.current;
      if (!node || node.scrollTop > 0) return;
      const firstWeek = node.querySelector<HTMLElement>(".calendar-week-row");
      const rowHeight = firstWeek?.offsetHeight || 92;
      node.scrollTop = Math.max(0, continuousWeeks.before * rowHeight - node.clientHeight / 3);
      updateContinuousViewport(node);
    });
    return () => window.cancelAnimationFrame(frame);
  }, [focusDate, mode]);

  useEffect(() => {
    if (mode !== "continuous" || !pendingContinuousScrollDayRef.current) return;
    const targetDay = pendingContinuousScrollDayRef.current;
    pendingContinuousScrollDayRef.current = null;
    const frame = window.requestAnimationFrame(() => {
      scrollContinuousDayIntoView(targetDay, "smooth");
    });
    return () => window.cancelAnimationFrame(frame);
  }, [days, mode]);

  async function loadCalendars() {
    setLoading(true);
    setError("");
    try {
      const response = await listCalendars(settings);
      setCalendars(response.calendars);
      const ids = response.calendars.map((calendar) => calendar.id);
      setVisibleCalendarIds((current) => {
        const next = current.filter((id) => ids.includes(id));
        return next.length > 0 ? next : ids;
      });
      setEventCalendarId((current) => current && ids.includes(current) ? current : ids[0] ?? "");
    } catch (err) {
      setError(errorText(err));
    } finally {
      setLoading(false);
    }
  }

  async function loadEvents() {
    setLoading(true);
    setError("");
    try {
      const response = await listCalendarEvents(settings, {
        start_at: visibleRange.start.toISOString(),
        end_at: visibleRange.end.toISOString()
      });
      setEvents(response.events);
    } catch (err) {
      setError(errorText(err));
      setEvents([]);
    } finally {
      setLoading(false);
    }
  }

  function toggleCalendarVisibility(calendarId: string) {
    setVisibleCalendarIds((current) => {
      const next = current.includes(calendarId) ? current.filter((id) => id !== calendarId) : [...current, calendarId];
      if (next.length > 0 && !next.includes(eventCalendarId)) setEventCalendarId(next[0]);
      return next;
    });
    setEventCalendarId((current) => current || calendarId);
  }

  function openCalendarEditor(calendar: CalendarCollection) {
    setCalendarEditDialog({
      calendar,
      eventCount: null,
      loadingEventCount: true,
      confirmingDelete: false
    });
    void listCalendarEvents(settings, { calendar_id: calendar.id })
      .then((response) => {
        setCalendarEditDialog((current) => current?.calendar.id === calendar.id
          ? { ...current, eventCount: response.events.length, loadingEventCount: false }
          : current);
      })
      .catch((err) => {
        setCalendarEditDialog((current) => current?.calendar.id === calendar.id
          ? { ...current, eventCount: null, loadingEventCount: false }
          : current);
        setError(errorText(err));
      });
  }

  async function handleCalendarSave(calendar: CalendarCollection, payload: { name: string; color: string }) {
    const name = payload.name.trim();
    if (!name || !canManageCalendars) return;
    setSaving(true);
    setError("");
    try {
      const updated = await updateCalendar(settings, calendar.id, {
        name,
        color: normalizeHexColor(payload.color) || DEFAULT_CALENDAR_COLOR
      });
      setCalendars((current) => current.map((item) => item.id === calendar.id ? updated : item));
      setCalendarEditDialog(null);
    } catch (err) {
      setError(errorText(err));
    } finally {
      setSaving(false);
    }
  }

  async function handleCalendarDelete(calendar: CalendarCollection, payload: CalendarCollectionDeletePayload) {
    if (!canDeleteCalendars) return;
    const currentDialog = calendarEditDialog;
    if (!currentDialog || currentDialog.calendar.id !== calendar.id) return;
    if (!currentDialog.confirmingDelete) {
      setCalendarEditDialog({ ...currentDialog, confirmingDelete: true });
      return;
    }
    setSaving(true);
    setError("");
    try {
      await deleteCalendar(settings, calendar.id, payload);
      setCalendarEditDialog(null);
      setCalendars((current) => current.filter((item) => item.id !== calendar.id));
      setVisibleCalendarIds((current) => current.filter((id) => id !== calendar.id));
      setEventCalendarId((current) => current === calendar.id ? "" : current);
      await loadCalendars();
      if (calendars.length > 1 || payload.event_action === "move") {
        await loadEvents();
      } else {
        setEvents([]);
      }
    } catch (err) {
      setError(errorText(err));
    }
    finally {
      setSaving(false);
    }
  }

  async function handleCreateCalendar(formEvent: FormEvent<HTMLFormElement>) {
    formEvent.preventDefault();
    const name = newCalendarName.trim();
    if (!name || !canManageCalendars) return;
    setSaving(true);
    setError("");
    try {
      const calendar = await createCalendar(settings, {
        name,
        color: normalizeHexColor(newCalendarColor) || DEFAULT_CALENDAR_COLOR,
        timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC"
      });
      setCalendars((current) => [...current, calendar].sort(compareCalendars));
      setVisibleCalendarIds((current) => current.includes(calendar.id) ? current : [...current, calendar.id]);
      setEventCalendarId(calendar.id);
      setNewCalendarName("");
      setNewCalendarColor(DEFAULT_CALENDAR_COLOR);
    } catch (err) {
      setError(errorText(err));
    } finally {
      setSaving(false);
    }
  }

  function moveFocus(direction: -1 | 1) {
    if (mode === "continuous") return;
    if (mode === "month") {
      setFocusDate((current) => addMonths(current, direction));
      return;
    }
    const daysToMove = mode === "day" ? 1 : 7;
    setFocusDate((current) => addDays(current, direction * daysToMove));
  }

  function handleToday() {
    const today = startOfDay(new Date());
    if (mode !== "continuous") {
      setFocusDate(today);
      return;
    }
    pendingContinuousScrollDayRef.current = today;
    setFocusDate(today);
    window.requestAnimationFrame(() => {
      if (pendingContinuousScrollDayRef.current) {
        pendingContinuousScrollDayRef.current = null;
        scrollContinuousDayIntoView(today, "smooth");
      }
    });
  }

  function handleContinuousScroll() {
    const node = scrollRef.current;
    if (!node || mode !== "continuous") return;
    updateContinuousViewport(node);
    if (node.scrollTop < 160) prependContinuousWeeks();
    if (node.scrollTop + node.clientHeight > node.scrollHeight - 220) appendContinuousWeeks();
  }

  function handleContinuousWheel(event: ReactWheelEvent<HTMLDivElement>) {
    const node = scrollRef.current;
    if (!node || mode !== "continuous") return;
    if (event.deltaY < 0 && node.scrollTop <= 0) prependContinuousWeeks();
  }

  function prependContinuousWeeks() {
    const node = scrollRef.current;
    if (!node || continuousPrependPendingRef.current) return;
    continuousPrependPendingRef.current = true;
    const oldHeight = node.scrollHeight;
    setContinuousWeeks((current) => ({ ...current, before: current.before + 4 }));
    window.requestAnimationFrame(() => {
      const nextNode = scrollRef.current;
      if (nextNode) nextNode.scrollTop += nextNode.scrollHeight - oldHeight;
      continuousPrependPendingRef.current = false;
    });
  }

  function updateContinuousViewport(node: HTMLDivElement) {
    const next = { scrollTop: node.scrollTop, height: node.clientHeight };
    setContinuousViewport((current) => (
      Math.abs(current.scrollTop - next.scrollTop) < 1 && Math.abs(current.height - next.height) < 1 ? current : next
    ));
  }

  function scrollContinuousDayIntoView(day: Date, behavior: ScrollBehavior = "auto") {
    const node = scrollRef.current;
    if (!node || days.length === 0) return;
    const firstWeek = startOfWeek(days[0]);
    const targetWeek = startOfWeek(day);
    const weekIndex = Math.max(0, Math.floor(daysBetweenCount(firstWeek, targetWeek) / 7));
    node.scrollTo({ top: weekIndex * CONTINUOUS_WEEK_ROW_HEIGHT, behavior });
    window.requestAnimationFrame(() => updateContinuousViewport(node));
  }

  function beginEventDrag(dragEvent: ReactDragEvent<HTMLElement>, action: CalendarDragAction) {
    if (!canWrite) {
      dragEvent.preventDefault();
      return;
    }
    dragActionRef.current = action;
    setDraggingEventId(action.event.id);
    dragEvent.dataTransfer.effectAllowed = "move";
    dragEvent.dataTransfer.setData("text/plain", action.event.id);
  }

  function handleEventDragStart(dragEvent: ReactDragEvent<HTMLElement>, calendarEvent: CalendarEvent) {
    beginEventDrag(dragEvent, { kind: "move", event: calendarEvent });
  }

  function handleEventResizeDragStart(dragEvent: ReactDragEvent<HTMLElement>, calendarEvent: CalendarEvent, edge: CalendarResizeEdge) {
    dragEvent.stopPropagation();
    if (calendarEvent.all_day) {
      dragEvent.preventDefault();
      return;
    }
    beginEventDrag(dragEvent, { kind: edge === "start" ? "resize-start" : "resize-end", event: calendarEvent });
  }

  function handleEventDragEnd() {
    dragActionRef.current = null;
    setDraggingEventId("");
    setDropTarget(null);
  }

  function allowEventDrop(dragEvent: ReactDragEvent<HTMLElement>, canDrop: (action: CalendarDragAction) => boolean): boolean {
    const action = dragActionRef.current;
    if (!canWrite || !action || !canDrop(action)) return false;
    dragEvent.preventDefault();
    dragEvent.dataTransfer.dropEffect = "move";
    return true;
  }

  function handleEventDragOverDay(dragEvent: ReactDragEvent<HTMLElement>, day: Date) {
    if (!allowEventDrop(dragEvent, (action) => action.kind === "move")) return;
    const key = dayKey(day);
    setDropTarget((current) => (current?.kind === "day" && current.key === key ? current : { kind: "day", key }));
  }

  function handleEventDragOverTime(dragEvent: ReactDragEvent<HTMLElement>, day: Date) {
    if (!allowEventDrop(dragEvent, (action) => action.kind === "move" || action.kind === "resize-start" || action.kind === "resize-end")) return;
    const key = dayKey(day);
    const minuteOfDay = dropSlotMinuteOfDay(dragEvent);
    setDropTarget((current) => (
      current?.kind === "time" && current.key === key && current.minuteOfDay === minuteOfDay
        ? current
        : { kind: "time", key, minuteOfDay }
    ));
  }

  function handleDropTargetLeave(dragEvent: ReactDragEvent<HTMLElement>) {
    const related = dragEvent.relatedTarget;
    if (related instanceof Node && dragEvent.currentTarget.contains(related)) return;
    setDropTarget(null);
  }

  function handleEventDropOnDay(dropEvent: ReactDragEvent<HTMLElement>, day: Date) {
    dropEvent.preventDefault();
    const action = dragActionRef.current;
    if (!action || action.kind !== "move" || !canWrite) return;
    setDropTarget(null);
    void moveEvent(action.event, moveEventToDay(action.event, day));
  }

  function handleEventDropOnTime(dropEvent: ReactDragEvent<HTMLElement>, day: Date, minuteOfDay: number) {
    dropEvent.preventDefault();
    const action = dragActionRef.current;
    if (!action || !canWrite) return;
    setDropTarget(null);
    const next = action.kind === "move"
      ? moveEventToTime(action.event, day, minuteOfDay)
      : resizeEventToTime(action.event, action.kind === "resize-start" ? "start" : "end", day, minuteOfDay);
    void moveEvent(action.event, next);
  }

  async function moveEvent(calendarEvent: CalendarEvent, next: { startAt: Date; endAt: Date | null; allDay: boolean }) {
    setSaving(true);
    setError("");
    try {
      await updateCalendarEvent(settings, calendarEvent.id, {
        start_at: next.startAt.toISOString(),
        end_at: next.endAt ? next.endAt.toISOString() : null,
        all_day: next.allDay
      });
      await loadEvents();
    } catch (err) {
      setError(errorText(err));
    } finally {
      handleEventDragEnd();
      setSaving(false);
    }
  }

  function appendContinuousWeeks() {
    if (continuousAppendPendingRef.current) return;
    continuousAppendPendingRef.current = true;
    setContinuousWeeks((current) => ({ ...current, after: current.after + 4 }));
    window.requestAnimationFrame(() => {
      continuousAppendPendingRef.current = false;
    });
  }

  async function handleSave(payload: CalendarEventCreatePayload, event: CalendarEvent | null) {
    setSaving(true);
    setError("");
    try {
      if (event) {
        await updateCalendarEvent(settings, event.id, payload);
      } else {
        await createCalendarEvent(settings, payload);
      }
      if (payload.calendar_id) {
        setVisibleCalendarIds((current) => current.includes(payload.calendar_id as string) ? current : [...current, payload.calendar_id as string]);
        setEventCalendarId(payload.calendar_id);
      }
      setEventDialog(null);
      await loadEvents();
    } catch (err) {
      setError(errorText(err));
    } finally {
      setSaving(false);
    }
  }

  async function handleDelete(event: CalendarEvent) {
    setSaving(true);
    setError("");
    try {
      await deleteCalendarEvent(settings, event.id);
      setEventDialog(null);
      await loadEvents();
    } catch (err) {
      setError(errorText(err));
    } finally {
      setSaving(false);
    }
  }

  return (
    <div className="workspace-data-page module-entry-page calendar-page calendar-fullscreen">
      {error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}

      <div className={`calendar-shell ${loading ? "is-loading" : ""}`}>
        <aside className="calendar-sidebar">
          <div className="calendar-sidebar-heading">Calendars</div>
          <div className="calendar-list">
            {calendars.map((calendar) => (
              <div key={calendar.id} className={calendar.id === targetCalendarId ? "calendar-list-row is-target" : "calendar-list-row"}>
                <button
                  type="button"
                  className={visibleCalendarIds.includes(calendar.id) ? "calendar-visibility-switch is-on" : "calendar-visibility-switch"}
                  role="switch"
                  aria-checked={visibleCalendarIds.includes(calendar.id)}
                  aria-label={`Show ${calendar.name}`}
                  title={`Show ${calendar.name}`}
                  style={{ "--calendar-list-color": normalizeHexColor(calendar.color) || DEFAULT_CALENDAR_COLOR } as CSSProperties}
                  onClick={() => toggleCalendarVisibility(calendar.id)}
                >
                  <span />
                </button>
                <button type="button" className="calendar-list-name" onClick={() => setEventCalendarId(calendar.id)}>
                  {calendar.name}
                </button>
                {(canManageCalendars || canDeleteCalendars) && (
                  <Button
                    type="button"
                    className="calendar-row-icon-button"
                    title={`Edit ${calendar.name}`}
                    aria-label={`Edit ${calendar.name}`}
                    onClick={() => openCalendarEditor(calendar)}
                  >
                    <Pencil size={15} />
                  </Button>
                )}
              </div>
            ))}
            {!calendars.length && !loading && (
              <p className="calendar-empty-note">No calendars</p>
            )}
            {canManageCalendars && (
              <form className="calendar-create-form" onSubmit={handleCreateCalendar}>
                <input
                  value={newCalendarName}
                  onChange={(event) => setNewCalendarName(event.target.value)}
                  placeholder="New calendar"
                  aria-label="New calendar name"
                  disabled={saving}
                  maxLength={255}
                />
                <input
                  type="color"
                  value={newCalendarColor}
                  aria-label="New calendar color"
                  disabled={saving}
                  onChange={(event) => setNewCalendarColor(event.target.value)}
                />
                <Button type="submit" className="calendar-icon-button" title="Add calendar" aria-label="Add calendar" disabled={saving || !newCalendarName.trim()}>
                  <Plus size={16} />
                </Button>
              </form>
            )}
          </div>
          <div className="calendar-sidebar-heading is-secondary">Agenda</div>
          <div className="calendar-agenda">
            {agendaGroups.map((group) => (
              <section key={group.key} className="calendar-agenda-group">
                <h3>{agendaDateLabel(group.date)}</h3>
                {group.events.map((event) => (
                  <button
                    key={`${group.key}-${event.id}`}
                    type="button"
                    className={["calendar-agenda-item", hoveredEventId === event.id ? "is-linked-hover" : ""].filter(Boolean).join(" ")}
                    style={calendarEventColorStyle(calendarColorById.get(event.calendar_id))}
                    onMouseEnter={() => setHoveredEventId(event.id)}
                    onMouseLeave={() => setHoveredEventId((current) => current === event.id ? "" : current)}
                    onFocus={() => setHoveredEventId(event.id)}
                    onBlur={() => setHoveredEventId((current) => current === event.id ? "" : current)}
                    onClick={() => setEventDialog({ kind: "edit", event })}
                  >
                    <EventInlineLabel event={event} />
                  </button>
                ))}
              </section>
            ))}
            {!agendaGroups.length && !loading && <p>No events</p>}
          </div>
        </aside>

        <section className="calendar-main-panel" aria-label="Calendar">
          <div className="calendar-view-toolbar" aria-label="Calendar controls">
            <div className="calendar-toolbar-left">
              <div className="calendar-mode-switch" role="tablist" aria-label="Calendar views">
                {modeOptions.map((option) => (
                  <button
                    key={option.id}
                    type="button"
                    role="tab"
                    aria-selected={mode === option.id}
                    className={mode === option.id ? "calendar-mode-tab is-active" : "calendar-mode-tab"}
                    onClick={() => setMode(option.id)}
                  >
                    {option.label}
                  </button>
                ))}
              </div>
            </div>

            <div className="calendar-toolbar-center">
              <div className="calendar-icon-group" aria-label="Calendar navigation">
                <Button type="button" className="calendar-icon-button" title="Previous" aria-label="Previous" onClick={() => moveFocus(-1)} disabled={mode === "continuous"}>
                  <ChevronLeft size={18} />
                </Button>
                <Button type="button" onClick={handleToday}>Today</Button>
                <Button type="button" className="calendar-icon-button" title="Next" aria-label="Next" onClick={() => moveFocus(1)} disabled={mode === "continuous"}>
                  <ChevronRight size={18} />
                </Button>
                <div className="calendar-range-label">
                  <CalendarDays size={18} aria-hidden="true" />
                  <strong>{heading}</strong>
                </div>
              </div>
            </div>

            <div className="calendar-toolbar-right">
              <Button type="button" className="calendar-icon-button" title="Refresh" aria-label="Refresh" onClick={() => void loadEvents()}>
                <RefreshCw size={18} />
              </Button>
              {canWrite && (
                <Button type="button" variant="primary" onClick={() => setEventDialog({ kind: "create" })} disabled={!targetCalendarId}>
                  <Plus size={17} /> New
                </Button>
              )}
            </div>
          </div>

          <div className={`calendar-view-shell is-${mode}`}>
            {loading && <div className="calendar-loading"><LoadingIndicator label="Loading calendar..." /></div>}
            {mode === "continuous" ? (
              <div className="calendar-continuous" ref={scrollRef} onScroll={handleContinuousScroll} onWheel={handleContinuousWheel}>
                <CalendarWeekRows
                  days={days}
                  eventsByDay={eventsByDay}
                  calendarColorById={calendarColorById}
                  focusDate={focusDate}
                  variant="continuous"
                  preferences={viewPreferences}
                  canWrite={canWrite}
                  draggingEventId={draggingEventId}
                  hoveredEventId={hoveredEventId}
                  dropTarget={dropTarget}
                  viewport={continuousViewport}
                  onEventSelect={(event) => setEventDialog({ kind: "edit", event })}
                  onEventHover={setHoveredEventId}
                  onEventDragStart={handleEventDragStart}
                  onEventDragEnd={handleEventDragEnd}
                  onEventDragOverDay={handleEventDragOverDay}
                  onDropTargetLeave={handleDropTargetLeave}
                  onEventDropOnDay={handleEventDropOnDay}
                />
              </div>
            ) : mode === "month" ? (
              <CalendarWeekRows
                days={days}
                eventsByDay={eventsByDay}
                calendarColorById={calendarColorById}
                focusDate={focusDate}
                variant="month"
                preferences={viewPreferences}
                canWrite={canWrite}
                draggingEventId={draggingEventId}
                hoveredEventId={hoveredEventId}
                dropTarget={dropTarget}
                onEventSelect={(event) => setEventDialog({ kind: "edit", event })}
                onEventHover={setHoveredEventId}
                onEventDragStart={handleEventDragStart}
                onEventDragEnd={handleEventDragEnd}
                onEventDragOverDay={handleEventDragOverDay}
                onDropTargetLeave={handleDropTargetLeave}
                onEventDropOnDay={handleEventDropOnDay}
              />
            ) : (
              <CalendarTimeGrid
                days={days}
                eventsByDay={eventsByDay}
                calendarColorById={calendarColorById}
                mode={mode}
                preferences={viewPreferences}
                canWrite={canWrite}
                draggingEventId={draggingEventId}
                hoveredEventId={hoveredEventId}
                dropTarget={dropTarget}
                onEventSelect={(event) => setEventDialog({ kind: "edit", event })}
                onEventHover={setHoveredEventId}
                onEventDragStart={handleEventDragStart}
                onEventResizeDragStart={handleEventResizeDragStart}
                onEventDragEnd={handleEventDragEnd}
                onEventDragOverDay={handleEventDragOverDay}
                onEventDragOverTime={handleEventDragOverTime}
                onDropTargetLeave={handleDropTargetLeave}
                onEventDropOnDay={handleEventDropOnDay}
                onEventDropOnTime={handleEventDropOnTime}
              />
            )}
          </div>
        </section>
      </div>

      {eventDialog && calendars.length > 0 && (
        <CalendarEventDialog
          calendars={calendars}
          defaultCalendarId={targetCalendarId}
          event={eventDialog.kind === "edit" ? eventDialog.event : null}
          focusDate={focusDate}
          saving={saving}
          canWrite={canWrite}
          canDelete={canDelete}
          onCancel={() => setEventDialog(null)}
          onSave={handleSave}
          onDelete={handleDelete}
        />
      )}
      {calendarEditDialog && (
          <CalendarCollectionDialog
            key={calendarEditDialog.calendar.id}
            state={calendarEditDialog}
            calendars={calendars}
            saving={saving}
            canWrite={canManageCalendars}
            canDelete={canDeleteCalendars}
          onCancel={() => setCalendarEditDialog(null)}
          onSave={handleCalendarSave}
          onDelete={handleCalendarDelete}
        />
      )}
    </div>
  );
}

function CalendarWeekRows({
  days,
  eventsByDay,
  calendarColorById,
  focusDate,
  variant,
  preferences,
  canWrite,
  draggingEventId,
  hoveredEventId,
  dropTarget,
  viewport,
  onEventSelect,
  onEventHover,
  onEventDragStart,
  onEventDragEnd,
  onEventDragOverDay,
  onDropTargetLeave,
  onEventDropOnDay
}: {
  days: Date[];
  eventsByDay: Map<string, CalendarEvent[]>;
  calendarColorById: Map<string, string>;
  focusDate: Date;
  variant: "month" | "continuous";
  preferences: CalendarViewPreferences;
  canWrite: boolean;
  draggingEventId: string;
  hoveredEventId: string;
  dropTarget: CalendarDropTarget;
  viewport?: ContinuousViewport;
  onEventSelect: (event: CalendarEvent) => void;
  onEventHover: (eventId: string) => void;
  onEventDragStart: (dragEvent: ReactDragEvent<HTMLElement>, calendarEvent: CalendarEvent) => void;
  onEventDragEnd: () => void;
  onEventDragOverDay: (dragEvent: ReactDragEvent<HTMLElement>, day: Date) => void;
  onDropTargetLeave: (dragEvent: ReactDragEvent<HTMLElement>) => void;
  onEventDropOnDay: (dropEvent: ReactDragEvent<HTMLElement>, day: Date) => void;
}) {
  const weeks = chunk(days, 7);
  const virtualWindow = variant === "continuous" && preferences.continuousVirtualization
    ? continuousVirtualWindow(weeks.length, viewport ?? { scrollTop: 0, height: 0 }, preferences.continuousOverscanWeeks)
    : { start: 0, end: weeks.length, topSpacerHeight: 0, bottomSpacerHeight: 0 };
  const visibleWeeks = weeks.slice(virtualWindow.start, virtualWindow.end);
  return (
    <div className={`calendar-week-rows is-${variant}`}>
      <div className="calendar-weekday-header">
        {["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].map((label) => <span key={label}>{label}</span>)}
      </div>
      {virtualWindow.topSpacerHeight > 0 && <div className="calendar-week-spacer" style={{ height: virtualWindow.topSpacerHeight }} aria-hidden="true" />}
      {visibleWeeks.map((week) => (
        <div className="calendar-week-row" key={dayKey(week[0])}>
          {week.map((day) => {
            const key = dayKey(day);
            const dayEvents = eventsByDay.get(key) ?? [];
            const outsideFocusMonth = variant === "month" && !sameMonth(day, focusDate);
            return (
              <section
                key={key}
                className={[
                  "calendar-day-cell",
                  outsideFocusMonth ? "is-muted" : "",
                  sameDay(day, new Date()) ? "is-today" : "",
                  dropTarget?.kind === "day" && dropTarget.key === key ? "is-drop-target" : "",
                  variant === "continuous" && preferences.alternateContinuousMonths ? (day.getMonth() % 2 === 0 ? "is-even-month" : "is-odd-month") : ""
                ].filter(Boolean).join(" ")}
                onDragOver={canWrite ? (event) => onEventDragOverDay(event, day) : undefined}
                onDragLeave={canWrite ? onDropTargetLeave : undefined}
                onDrop={canWrite ? (event) => onEventDropOnDay(event, day) : undefined}
              >
                <header>
                  <span className="calendar-day-number">{day.getDate()}</span>
                  {day.getDate() === 1 && <small>{monthYearLabel(day)}</small>}
                </header>
                <div className="calendar-event-stack">
                  {dayEvents.slice(0, variant === "month" ? 5 : 3).map((event) => (
                    <CalendarEventChip
                      key={event.id}
                      event={event}
                      color={calendarColorById.get(event.calendar_id)}
                      canDrag={canWrite}
                      dragging={draggingEventId === event.id}
                      linkedHover={hoveredEventId === event.id}
                      onSelect={onEventSelect}
                      onHover={onEventHover}
                      onDragStart={onEventDragStart}
                      onDragEnd={onEventDragEnd}
                    />
                  ))}
                  {dayEvents.length > (variant === "month" ? 5 : 3) && <span className="calendar-more-events">+{dayEvents.length - (variant === "month" ? 5 : 3)}</span>}
                </div>
              </section>
            );
          })}
        </div>
      ))}
      {virtualWindow.bottomSpacerHeight > 0 && <div className="calendar-week-spacer" style={{ height: virtualWindow.bottomSpacerHeight }} aria-hidden="true" />}
    </div>
  );
}

function CalendarTimeGrid({
  days,
  eventsByDay,
  calendarColorById,
  mode,
  preferences,
  canWrite,
  draggingEventId,
  hoveredEventId,
  dropTarget,
  onEventSelect,
  onEventHover,
  onEventDragStart,
  onEventResizeDragStart,
  onEventDragEnd,
  onEventDragOverDay,
  onEventDragOverTime,
  onDropTargetLeave,
  onEventDropOnDay,
  onEventDropOnTime
}: {
  days: Date[];
  eventsByDay: Map<string, CalendarEvent[]>;
  calendarColorById: Map<string, string>;
  mode: CalendarMode;
  preferences: CalendarViewPreferences;
  canWrite: boolean;
  draggingEventId: string;
  hoveredEventId: string;
  dropTarget: CalendarDropTarget;
  onEventSelect: (event: CalendarEvent) => void;
  onEventHover: (eventId: string) => void;
  onEventDragStart: (dragEvent: ReactDragEvent<HTMLElement>, calendarEvent: CalendarEvent) => void;
  onEventResizeDragStart: (dragEvent: ReactDragEvent<HTMLElement>, calendarEvent: CalendarEvent, edge: CalendarResizeEdge) => void;
  onEventDragEnd: () => void;
  onEventDragOverDay: (dragEvent: ReactDragEvent<HTMLElement>, day: Date) => void;
  onEventDragOverTime: (dragEvent: ReactDragEvent<HTMLElement>, day: Date) => void;
  onDropTargetLeave: (dragEvent: ReactDragEvent<HTMLElement>) => void;
  onEventDropOnDay: (dropEvent: ReactDragEvent<HTMLElement>, day: Date) => void;
  onEventDropOnTime: (dropEvent: ReactDragEvent<HTMLElement>, day: Date, minuteOfDay: number) => void;
}) {
  const scrollRef = useRef<HTMLDivElement | null>(null);
  const hours = Array.from({ length: 24 }, (_item, index) => index);
  const columns = `72px repeat(${days.length}, minmax(132px, 1fr))`;
  const dimWeekends = preferences.dimWeekends && mode === "week";
  const gridStyle = timeGridStyle(columns, preferences);
  const allDayEventsByDay = days.map((day) => ({
    key: dayKey(day),
    date: day,
    events: (eventsByDay.get(dayKey(day)) ?? []).filter((event) => event.all_day)
  }));
  const hasAllDayEvents = allDayEventsByDay.some((day) => day.events.length > 0);

  useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = INITIAL_SCROLL_HOUR * HOUR_ROW_HEIGHT;
  }, [days.length, days[0]?.getTime()]);

  return (
    <div className="calendar-time-view">
      <div className="calendar-time-header" style={{ gridTemplateColumns: columns }}>
        <div className="calendar-time-corner" />
        {days.map((day) => (
          <header key={dayKey(day)} className={[sameDay(day, new Date()) ? "is-today" : "", dimWeekends && isWeekend(day) ? "is-weekend" : ""].filter(Boolean).join(" ")}>
            <strong>{weekdayLong(day)}</strong>
            <span>{dayMonthLabel(day)}</span>
          </header>
        ))}
      </div>
      {hasAllDayEvents && (
        <div className="calendar-all-day-strip" style={{ gridTemplateColumns: columns }}>
          <div className="calendar-all-day-label">All day</div>
          {allDayEventsByDay.map((day) => (
            <div
              key={day.key}
              className={[
                "calendar-all-day-cell",
                dimWeekends && isWeekend(day.date) ? "is-weekend" : "",
                dropTarget?.kind === "day" && dropTarget.key === day.key ? "is-drop-target" : ""
              ].filter(Boolean).join(" ")}
              onDragOver={canWrite ? (event) => onEventDragOverDay(event, day.date) : undefined}
              onDragLeave={canWrite ? onDropTargetLeave : undefined}
              onDrop={canWrite ? (event) => onEventDropOnDay(event, day.date) : undefined}
            >
              {day.events.map((event) => (
                <CalendarEventChip
                  key={event.id}
                  event={event}
                  color={calendarColorById.get(event.calendar_id)}
                  compact
                  canDrag={canWrite}
                  dragging={draggingEventId === event.id}
                  linkedHover={hoveredEventId === event.id}
                  onSelect={onEventSelect}
                  onHover={onEventHover}
                  onDragStart={onEventDragStart}
                  onDragEnd={onEventDragEnd}
                />
              ))}
            </div>
          ))}
        </div>
      )}
      <div className="calendar-time-scroll" ref={scrollRef}>
        <div className="calendar-time-grid" style={gridStyle}>
          <div className="calendar-hour-label-column">
            {hours.map((hour) => <div className="calendar-hour-label" key={hour}>{String(hour).padStart(2, "0")}:00</div>)}
          </div>
          {days.map((day) => (
            <TimedDayColumn
              key={dayKey(day)}
              day={day}
              events={eventsByDay.get(dayKey(day)) ?? []}
              calendarColorById={calendarColorById}
              dimWeekend={dimWeekends && isWeekend(day)}
              canWrite={canWrite}
              draggingEventId={draggingEventId}
              hoveredEventId={hoveredEventId}
              dropTarget={dropTarget?.kind === "time" && dropTarget.key === dayKey(day) ? dropTarget : null}
              onEventSelect={onEventSelect}
              onEventHover={onEventHover}
              onEventDragStart={onEventDragStart}
              onEventResizeDragStart={onEventResizeDragStart}
              onEventDragEnd={onEventDragEnd}
              onEventDragOverTime={onEventDragOverTime}
              onDropTargetLeave={onDropTargetLeave}
              onEventDropOnTime={onEventDropOnTime}
            />
          ))}
        </div>
      </div>
    </div>
  );
}

function TimedDayColumn({
  day,
  events,
  calendarColorById,
  dimWeekend,
  canWrite,
  draggingEventId,
  hoveredEventId,
  dropTarget,
  onEventSelect,
  onEventHover,
  onEventDragStart,
  onEventResizeDragStart,
  onEventDragEnd,
  onEventDragOverTime,
  onDropTargetLeave,
  onEventDropOnTime
}: {
  day: Date;
  events: CalendarEvent[];
  calendarColorById: Map<string, string>;
  dimWeekend: boolean;
  canWrite: boolean;
  draggingEventId: string;
  hoveredEventId: string;
  dropTarget: CalendarTimeDropTarget | null;
  onEventSelect: (event: CalendarEvent) => void;
  onEventHover: (eventId: string) => void;
  onEventDragStart: (dragEvent: ReactDragEvent<HTMLElement>, calendarEvent: CalendarEvent) => void;
  onEventResizeDragStart: (dragEvent: ReactDragEvent<HTMLElement>, calendarEvent: CalendarEvent, edge: CalendarResizeEdge) => void;
  onEventDragEnd: () => void;
  onEventDragOverTime: (dragEvent: ReactDragEvent<HTMLElement>, day: Date) => void;
  onDropTargetLeave: (dragEvent: ReactDragEvent<HTMLElement>) => void;
  onEventDropOnTime: (dropEvent: ReactDragEvent<HTMLElement>, day: Date, minuteOfDay: number) => void;
}) {
  const layout = useMemo(() => layoutTimedEventsForDay(events, day), [day, events]);
  return (
    <div
      className={["calendar-day-time-column", dimWeekend ? "is-weekend" : "", dropTarget ? "is-drop-target" : ""].filter(Boolean).join(" ")}
      onDragOver={canWrite ? (event) => onEventDragOverTime(event, day) : undefined}
      onDragLeave={canWrite ? onDropTargetLeave : undefined}
      onDrop={canWrite ? (event) => onEventDropOnTime(event, day, dropSlotMinuteOfDay(event)) : undefined}
    >
      {dropTarget && (
        <div
          className="calendar-time-drop-marker"
          style={{ top: `${dropTarget.minuteOfDay / 60 * HOUR_ROW_HEIGHT}px` }}
          aria-hidden="true"
        >
          <span>{minuteOfDayLabel(dropTarget.minuteOfDay)}</span>
        </div>
      )}
      {layout.items.map((item) => (
        <div
          key={item.event.id}
          className={[
            "calendar-timed-event",
            draggingEventId === item.event.id ? "is-dragging" : "",
            hoveredEventId === item.event.id ? "is-linked-hover" : ""
          ].filter(Boolean).join(" ")}
          style={timedEventStyle(item, calendarColorById.get(item.event.calendar_id))}
          draggable={canWrite}
          onMouseEnter={() => onEventHover(item.event.id)}
          onMouseLeave={() => onEventHover("")}
          onDragStart={canWrite ? (event) => onEventDragStart(event, item.event) : undefined}
          onDragEnd={canWrite ? onEventDragEnd : undefined}
          title={`Edit ${item.event.summary}`}
        >
          {canWrite && (
            <span
              className="calendar-event-resize-handle is-start"
              draggable
              title="Change start time"
              onDragStart={(event) => onEventResizeDragStart(event, item.event, "start")}
              onDragEnd={onEventDragEnd}
            />
          )}
          <button
            type="button"
            className="calendar-timed-event-content"
            onClick={() => onEventSelect(item.event)}
            onFocus={() => onEventHover(item.event.id)}
            onBlur={() => onEventHover("")}
            title={`Edit ${item.event.summary}`}
          >
            <EventInlineLabel event={item.event} />
          </button>
          {canWrite && (
            <span
              className="calendar-event-resize-handle is-end"
              draggable
              title="Change end time"
              onDragStart={(event) => onEventResizeDragStart(event, item.event, "end")}
              onDragEnd={onEventDragEnd}
            />
          )}
        </div>
      ))}
      {layout.overflows.map((overflow) => (
        <button
          key={overflow.key}
          type="button"
          className="calendar-timed-overflow"
          style={timedOverflowStyle(overflow)}
          title={overflow.events.map((event) => event.summary).join(", ")}
          onClick={() => onEventSelect(overflow.events[0])}
        >
          +{overflow.events.length}
        </button>
      ))}
    </div>
  );
}

function CalendarEventChip({
  event,
  color,
  compact = false,
  canDrag = false,
  dragging = false,
  linkedHover = false,
  onSelect,
  onHover,
  onDragStart,
  onDragEnd
}: {
  event: CalendarEvent;
  color?: string | null;
  compact?: boolean;
  canDrag?: boolean;
  dragging?: boolean;
  linkedHover?: boolean;
  onSelect: (event: CalendarEvent) => void;
  onHover?: (eventId: string) => void;
  onDragStart?: (dragEvent: ReactDragEvent<HTMLElement>, calendarEvent: CalendarEvent) => void;
  onDragEnd?: () => void;
}) {
  return (
    <button
      type="button"
      className={[
        compact ? "calendar-event-chip is-compact" : "calendar-event-chip",
        dragging ? "is-dragging" : "",
        linkedHover ? "is-linked-hover" : ""
      ].filter(Boolean).join(" ")}
      style={calendarEventColorStyle(color)}
      draggable={canDrag}
      onClick={() => onSelect(event)}
      onMouseEnter={onHover ? () => onHover(event.id) : undefined}
      onMouseLeave={onHover ? () => onHover("") : undefined}
      onFocus={onHover ? () => onHover(event.id) : undefined}
      onBlur={onHover ? () => onHover("") : undefined}
      onDragStart={canDrag && onDragStart ? (dragEvent) => onDragStart(dragEvent, event) : undefined}
      onDragEnd={canDrag ? onDragEnd : undefined}
      title={`Edit ${event.summary}`}
    >
      <EventInlineLabel event={event} />
    </button>
  );
}

function CalendarCollectionDialog({
  state,
  calendars,
  saving,
  canWrite,
  canDelete,
  onCancel,
  onSave,
  onDelete
}: {
  state: CalendarEditDialogState;
  calendars: CalendarCollection[];
  saving: boolean;
  canWrite: boolean;
  canDelete: boolean;
  onCancel: () => void;
  onSave: (calendar: CalendarCollection, payload: { name: string; color: string }) => Promise<void>;
  onDelete: (calendar: CalendarCollection, payload: CalendarCollectionDeletePayload) => Promise<void>;
}) {
  const { calendar, eventCount, loadingEventCount, confirmingDelete } = state;
  const [name, setName] = useState(calendar.name);
  const [color, setColor] = useState(normalizeHexColor(calendar.color) || DEFAULT_CALENDAR_COLOR);
  const moveTargets = calendars.filter((item) => item.id !== calendar.id);
  const firstMoveTargetId = moveTargets[0]?.id || "";
  const [eventAction, setEventAction] = useState<CalendarDeleteEventAction>("delete");
  const [targetCalendarId, setTargetCalendarId] = useState(firstMoveTargetId);
  const [makeTargetDefault, setMakeTargetDefault] = useState(calendar.is_default && Boolean(firstMoveTargetId));
  const formId = "calendar-collection-form";
  const actionLabel = calendarDeleteActionLabel(calendar);
  const actionVerb = actionLabel.toLowerCase();
  const hasEvents = (eventCount ?? 0) > 0;
  const canMoveEvents = moveTargets.length > 0;
  const effectiveEventAction: CalendarDeleteEventAction = hasEvents ? eventAction : "delete";
  const deleteDisabled = (
    saving ||
    loadingEventCount ||
    eventCount === null ||
    !canDelete ||
    (hasEvents && effectiveEventAction === "move" && !targetCalendarId)
  );

  function submit(formEvent: FormEvent<HTMLFormElement>) {
    formEvent.preventDefault();
    void onSave(calendar, { name, color });
  }

  function submitDelete() {
    const payload: CalendarCollectionDeletePayload = {
      event_action: effectiveEventAction,
      target_calendar_id: effectiveEventAction === "move" ? targetCalendarId : null,
      make_target_default: effectiveEventAction === "move" && calendar.is_default && makeTargetDefault
    };
    void onDelete(calendar, payload);
  }

  return (
    <Dialog
      open
      title="Edit calendar"
      className="calendar-event-dialog"
      footerClassName="calendar-event-dialog-footer"
      closeDisabled={saving}
      onClose={onCancel}
      footer={(
        <>
          <div>
            {canDelete && (
              <Button type="button" variant="danger" onClick={submitDelete} disabled={deleteDisabled}>
                <Trash2 size={16} /> {confirmingDelete ? `Confirm ${actionVerb}` : actionLabel}
              </Button>
            )}
          </div>
          <div className="calendar-dialog-actions">
            <Button type="button" onClick={onCancel} disabled={saving}>Cancel</Button>
            <Button type="submit" form={formId} variant="primary" disabled={saving || !canWrite || !name.trim()}>{saving ? "Saving..." : "Save"}</Button>
          </div>
        </>
      )}
    >
      <form id={formId} className="calendar-dialog-form" onSubmit={submit}>
        {confirmingDelete && (
          <p className="calendar-delete-warning">
            {calendarDeleteWarning(calendar, eventCount ?? 0, effectiveEventAction, targetCalendarId, moveTargets)}
          </p>
        )}
        {!loadingEventCount && eventCount === null && canDelete && <p className="calendar-form-note">Event count could not be loaded.</p>}
        <div className="calendar-dialog-name-color-row">
          <label>
            <span>Name</span>
            <input value={name} onChange={(item) => setName(item.target.value)} required maxLength={255} autoFocus disabled={saving || !canWrite} />
          </label>
          <label className="calendar-dialog-color-label">
            <span>Color</span>
            <input type="color" value={color} onChange={(item) => setColor(item.target.value)} disabled={saving || !canWrite} />
          </label>
        </div>
        {canDelete && hasEvents && (
          <fieldset className="calendar-delete-options" disabled={saving || loadingEventCount}>
            <legend>{eventCount} event{eventCount === 1 ? "" : "s"}</legend>
            <label>
              <input
                type="radio"
                name="calendar-delete-event-action"
                value="delete"
                checked={eventAction === "delete"}
                onChange={() => setEventAction("delete")}
              />
              <span>{calendarEventDeleteOptionLabel(calendar)}</span>
            </label>
            <label className={!canMoveEvents ? "is-disabled" : ""}>
              <input
                type="radio"
                name="calendar-delete-event-action"
                value="move"
                checked={eventAction === "move"}
                onChange={() => setEventAction("move")}
                disabled={!canMoveEvents}
              />
              <span>Move events to another calendar</span>
            </label>
            {eventAction === "move" && canMoveEvents && (
              <>
                <label className="calendar-delete-target-label">
                  <span>Target calendar</span>
                  <select value={targetCalendarId} onChange={(item) => setTargetCalendarId(item.target.value)} required>
                    {moveTargets.map((item) => (
                      <option key={item.id} value={item.id}>{item.name}</option>
                    ))}
                  </select>
                </label>
                {calendar.is_default && (
                  <label className="calendar-checkbox-row">
                    <input type="checkbox" checked={makeTargetDefault} onChange={(item) => setMakeTargetDefault(item.target.checked)} />
                    <span>Make target calendar the default</span>
                  </label>
                )}
              </>
            )}
            {!canMoveEvents && <p className="calendar-form-note">No other calendar is available, so events can only be {calendarIsExternal(calendar) ? "removed locally" : "deleted"}.</p>}
          </fieldset>
        )}
      </form>
    </Dialog>
  );
}

function EventInlineLabel({ event }: { event: CalendarEvent }) {
  return (
    <span className="calendar-event-line">
      <span className="calendar-event-time">{eventTimeLabel(event)}</span>
      <span className="calendar-event-separator">-</span>
      <strong>{event.summary}</strong>
    </span>
  );
}

function CalendarEventDialog({
  calendars,
  defaultCalendarId,
  event,
  focusDate,
  saving,
  canWrite,
  canDelete,
  onCancel,
  onSave,
  onDelete
}: {
  calendars: CalendarCollection[];
  defaultCalendarId: string;
  event: CalendarEvent | null;
  focusDate: Date;
  saving: boolean;
  canWrite: boolean;
  canDelete: boolean;
  onCancel: () => void;
  onSave: (payload: CalendarEventCreatePayload, event: CalendarEvent | null) => Promise<void>;
  onDelete: (event: CalendarEvent) => Promise<void>;
}) {
  const initialStart = event ? new Date(event.start_at) : focusDate;
  const initialEnd = event?.end_at ? new Date(event.end_at) : addHours(initialStart, 1);
  const initialAllDayEnd = event?.all_day && event.end_at ? addDays(startOfDay(initialEnd), -1) : initialEnd;
  const [summary, setSummary] = useState(event?.summary ?? "");
  const [calendarId, setCalendarId] = useState(event?.calendar_id ?? defaultCalendarId);
  const [description, setDescription] = useState(event?.description ?? "");
  const [location, setLocation] = useState(event?.location ?? "");
  const [startDate, setStartDate] = useState(toInputDate(initialStart));
  const [endDate, setEndDate] = useState(toInputDate(initialAllDayEnd < initialStart ? initialStart : initialAllDayEnd));
  const [startTime, setStartTime] = useState(toInputTime(initialStart));
  const [endTime, setEndTime] = useState(toInputTime(initialEnd));
  const [allDay, setAllDay] = useState(event?.all_day ?? false);
  const [formError, setFormError] = useState("");
  const [confirmingDelete, setConfirmingDelete] = useState(false);
  const formId = "calendar-event-form";

  function handleAllDayChange(next: boolean) {
    setAllDay(next);
    if (next) {
      setStartTime("00:00");
      setEndTime("00:00");
    } else if (startTime === "00:00" && endTime === "00:00") {
      setStartTime("09:00");
      setEndTime("10:00");
    }
  }

  function handleStartDateChange(next: string) {
    setStartDate(next);
    if (endDate < next) setEndDate(next);
  }

  function handleStartTimeChange(next: string) {
    setStartTime(next);
    if (startDate === endDate && endTime <= next) setEndTime(addMinutesToTime(next, 60));
  }

  async function submit(formEvent: FormEvent<HTMLFormElement>) {
    formEvent.preventDefault();
    setFormError("");
    const startAt = allDay ? localDateTime(startDate, "00:00") : localDateTime(startDate, startTime);
    const endAt = allDay ? addDays(localDateTime(endDate, "00:00"), 1) : localDateTime(endDate, endTime);
    if (endAt <= startAt) {
      setFormError(allDay ? "End date must be on or after start date." : "End date and time must be after start date and time.");
      return;
    }
    await onSave({
      calendar_id: calendarId,
      summary,
      description: description || null,
      location: location || null,
      start_at: startAt.toISOString(),
      end_at: endAt.toISOString(),
      all_day: allDay
    }, event);
  }

  function requestDelete() {
    if (!event) return;
    if (!confirmingDelete) {
      setConfirmingDelete(true);
      return;
    }
    void onDelete(event);
  }

  return (
    <Dialog
      open
      title={event ? "Edit event" : "New event"}
      className="calendar-event-dialog"
      footerClassName="calendar-event-dialog-footer"
      closeDisabled={saving}
      onClose={onCancel}
      footer={(
        <>
          <div>
            {event && canDelete && (
              <Button type="button" variant="danger" onClick={requestDelete} disabled={saving}>
                <Trash2 size={16} /> {confirmingDelete ? "Confirm delete" : "Delete"}
              </Button>
            )}
          </div>
          <div className="calendar-dialog-actions">
            <Button type="button" onClick={onCancel} disabled={saving}>Cancel</Button>
            <Button type="submit" form={formId} variant="primary" disabled={saving || !canWrite}>{saving ? "Saving..." : "Save"}</Button>
          </div>
        </>
      )}
    >
      <form id={formId} className="calendar-dialog-form" onSubmit={submit}>
        {formError && <p className="calendar-form-error">{formError}</p>}
        <label>
          <span>Title</span>
          <input value={summary} onChange={(item) => setSummary(item.target.value)} required maxLength={500} autoFocus disabled={saving || !canWrite} />
        </label>
        <label>
          <span>Calendar</span>
          <select value={calendarId} onChange={(item) => setCalendarId(item.target.value)} required disabled={saving || !canWrite}>
            {calendars.map((calendar) => (
              <option key={calendar.id} value={calendar.id}>{calendar.name}</option>
            ))}
          </select>
        </label>
        <label>
          <span>Description</span>
          <textarea value={description} onChange={(item) => setDescription(item.target.value)} rows={4} disabled={saving || !canWrite} />
        </label>
        <label>
          <span>Location</span>
          <input value={location} onChange={(item) => setLocation(item.target.value)} maxLength={500} disabled={saving || !canWrite} />
        </label>
        <ToggleSwitch label="Whole day" checked={allDay} disabled={saving || !canWrite} onChange={handleAllDayChange} />
        <div className="calendar-dialog-date-row">
          <label>
            <span>Start date</span>
            <input type="date" value={startDate} onChange={(item) => handleStartDateChange(item.target.value)} required disabled={saving || !canWrite} />
          </label>
          <label>
            <span>Start time</span>
            <input type="time" value={startTime} onChange={(item) => handleStartTimeChange(item.target.value)} disabled={saving || !canWrite || allDay} />
          </label>
        </div>
        <div className="calendar-dialog-date-row">
          <label>
            <span>End date</span>
            <input type="date" value={endDate} min={startDate} onChange={(item) => setEndDate(item.target.value)} required disabled={saving || !canWrite} />
          </label>
          <label>
            <span>End time</span>
            <input type="time" value={endTime} min={!allDay && startDate === endDate ? startTime : undefined} onChange={(item) => setEndTime(item.target.value)} disabled={saving || !canWrite || allDay} />
          </label>
        </div>
      </form>
    </Dialog>
  );
}

function rangeForMode(mode: CalendarMode, focusDate: Date, continuousWeeks: { before: number; after: number }): Range {
  if (mode === "month") {
    const start = startOfWeek(startOfMonth(focusDate));
    return { start, end: addDays(start, 42) };
  }
  if (mode === "day") return { start: startOfDay(focusDate), end: addDays(startOfDay(focusDate), 1) };
  if (mode === "continuous") {
    const start = addDays(startOfWeek(focusDate), -continuousWeeks.before * 7);
    const end = addDays(startOfWeek(focusDate), continuousWeeks.after * 7);
    return { start, end };
  }
  const start = startOfWeek(focusDate);
  return { start, end: addDays(start, mode === "workweek" ? 5 : 7) };
}

function daysForMode(mode: CalendarMode, focusDate: Date, continuousWeeks: { before: number; after: number }): Date[] {
  const range = rangeForMode(mode, focusDate, continuousWeeks);
  return daysBetween(range.start, range.end);
}

function groupEventsByDay(events: CalendarEvent[]): Map<string, CalendarEvent[]> {
  const grouped = new Map<string, CalendarEvent[]>();
  for (const event of events) {
    const start = new Date(event.start_at);
    const end = event.end_at ? new Date(event.end_at) : start;
    let cursor = startOfDay(start);
    let last = event.all_day && event.end_at ? addDays(startOfDay(end), -1) : startOfDay(end);
    if (last < cursor) last = cursor;
    while (cursor <= last) {
      const key = dayKey(cursor);
      grouped.set(key, [...(grouped.get(key) ?? []), event]);
      cursor = addDays(cursor, 1);
    }
  }
  for (const [key, items] of grouped.entries()) {
    grouped.set(key, items.sort((left, right) => left.start_at.localeCompare(right.start_at)));
  }
  return grouped;
}

function agendaGroupsForDays(days: Date[], eventsByDay: Map<string, CalendarEvent[]>): AgendaGroup[] {
  const groups: AgendaGroup[] = [];
  for (const day of days) {
    const key = dayKey(day);
    const events = (eventsByDay.get(key) ?? []).slice().sort(compareAgendaEvents);
    if (events.length > 0) groups.push({ key, date: day, events });
  }
  return groups;
}

function compareCalendars(left: CalendarCollection, right: CalendarCollection): number {
  if (left.is_default !== right.is_default) return left.is_default ? -1 : 1;
  return left.name.localeCompare(right.name);
}

function calendarDeleteActionLabel(calendar: CalendarCollection): string {
  return calendarIsExternal(calendar) ? "Remove" : "Delete";
}

function calendarEventDeleteOptionLabel(calendar: CalendarCollection): string {
  return calendarIsExternal(calendar) ? "Remove local events with this calendar" : "Delete events with this calendar";
}

function calendarDeleteWarning(
  calendar: CalendarCollection,
  eventCount: number,
  eventAction: CalendarDeleteEventAction,
  targetCalendarId: string,
  moveTargets: CalendarCollection[]
): string {
  const action = calendarIsExternal(calendar) ? "Removing" : "Deleting";
  const eventWord = eventCount === 1 ? "event" : "events";
  if (eventAction === "move") {
    const target = moveTargets.find((item) => item.id === targetCalendarId);
    return `${action} this calendar will move ${eventCount} ${eventWord} to ${target?.name || "the selected calendar"}.`;
  }
  if (calendarIsExternal(calendar)) {
    return `${action} this calendar will remove ${eventCount} local ${eventWord} from GovOPlaN.`;
  }
  return `${action} this calendar will delete ${eventCount} ${eventWord}.`;
}

function calendarIsExternal(calendar: CalendarCollection): boolean {
  const metadata = calendar.metadata;
  if (!metadata || typeof metadata !== "object") return false;
  const sourceKind = metadataText(metadata, "source_kind") || metadataText(metadata, "sourceKind") || metadataText(metadata, "connector_kind") || metadataText(metadata, "connectorKind");
  if (sourceKind && sourceKind !== "local") return true;
  const connectionKind = metadataText(metadata, "connection_kind") || metadataText(metadata, "connectionKind");
  if (connectionKind && connectionKind !== "local") return true;
  return Boolean(
    metadataFlag(metadata, "external") ||
    metadataFlag(metadata, "remote") ||
    metadataFlag(metadata, "sync") ||
    metadataFlag(metadata, "caldav") ||
    metadataText(metadata, "connector_id") ||
    metadataText(metadata, "connectorId") ||
    metadataText(metadata, "sync_href") ||
    metadataText(metadata, "syncHref")
  );
}

function metadataText(metadata: Record<string, unknown>, key: string): string {
  const value = metadata[key];
  return typeof value === "string" ? value.trim().toLowerCase() : "";
}

function metadataFlag(metadata: Record<string, unknown>, key: string): boolean {
  return metadata[key] === true;
}

function compareAgendaEvents(left: CalendarEvent, right: CalendarEvent): number {
  if (left.all_day !== right.all_day) return left.all_day ? -1 : 1;
  return left.start_at.localeCompare(right.start_at) || left.summary.localeCompare(right.summary);
}

function eventTimeLabel(event: CalendarEvent): string {
  if (event.all_day) return "All day";
  const start = new Date(event.start_at);
  const end = event.end_at ? new Date(event.end_at) : null;
  return end ? `${timeLabel(start)}-${timeLabel(end)}` : timeLabel(start);
}

function headingForMode(mode: CalendarMode, focusDate: Date, range: Range): string {
  if (mode === "day") return dayMonthYearLabel(focusDate);
  if (mode === "month") return monthYearLabel(focusDate);
  return `${dayMonthYearLabel(range.start)} - ${dayMonthYearLabel(addDays(range.end, -1))}`;
}

function chunk<T>(items: T[], size: number): T[][] {
  const rows: T[][] = [];
  for (let index = 0; index < items.length; index += size) rows.push(items.slice(index, index + size));
  return rows;
}

function startOfDay(value: Date): Date {
  return new Date(value.getFullYear(), value.getMonth(), value.getDate());
}

function startOfWeek(value: Date): Date {
  const day = value.getDay() || 7;
  return addDays(startOfDay(value), 1 - day);
}

function startOfMonth(value: Date): Date {
  return new Date(value.getFullYear(), value.getMonth(), 1);
}

function addDays(value: Date, days: number): Date {
  const next = new Date(value);
  next.setDate(next.getDate() + days);
  return next;
}

function addMonths(value: Date, months: number): Date {
  const next = new Date(value);
  next.setMonth(next.getMonth() + months);
  return next;
}

function addHours(value: Date, hours: number): Date {
  const next = new Date(value);
  next.setHours(next.getHours() + hours);
  return next;
}

function addMinutes(value: Date, minutes: number): Date {
  const next = new Date(value);
  next.setMinutes(next.getMinutes() + minutes);
  return next;
}

function daysBetween(start: Date, end: Date): Date[] {
  const days: Date[] = [];
  for (let cursor = startOfDay(start); cursor < end; cursor = addDays(cursor, 1)) days.push(cursor);
  return days;
}

function dayKey(value: Date): string {
  return toInputDate(value);
}

function sameDay(left: Date, right: Date): boolean {
  return left.getFullYear() === right.getFullYear() && left.getMonth() === right.getMonth() && left.getDate() === right.getDate();
}

function sameMonth(left: Date, right: Date): boolean {
  return left.getFullYear() === right.getFullYear() && left.getMonth() === right.getMonth();
}

function toInputDate(value: Date): string {
  return `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, "0")}-${String(value.getDate()).padStart(2, "0")}`;
}

function toInputTime(value: Date): string {
  return `${String(value.getHours()).padStart(2, "0")}:${String(value.getMinutes()).padStart(2, "0")}`;
}

function localDateTime(date: string, time: string): Date {
  return new Date(`${date}T${time || "00:00"}:00`);
}

function loadCalendarViewPreferences(): CalendarViewPreferences {
  if (typeof window === "undefined") return DEFAULT_CALENDAR_VIEW_PREFERENCES;
  try {
    const raw = window.localStorage.getItem(CALENDAR_VIEW_PREFERENCES_STORAGE_KEY);
    if (!raw) return DEFAULT_CALENDAR_VIEW_PREFERENCES;
    const parsed = JSON.parse(raw) as Partial<CalendarViewPreferences>;
    let workdayStartHour = clampNumber(parsed.workdayStartHour, 0, 23, DEFAULT_CALENDAR_VIEW_PREFERENCES.workdayStartHour);
    let workdayEndHour = clampNumber(parsed.workdayEndHour, 1, 24, DEFAULT_CALENDAR_VIEW_PREFERENCES.workdayEndHour);
    if (workdayEndHour <= workdayStartHour) {
      workdayStartHour = DEFAULT_CALENDAR_VIEW_PREFERENCES.workdayStartHour;
      workdayEndHour = DEFAULT_CALENDAR_VIEW_PREFERENCES.workdayEndHour;
    }
    return {
      dimWeekends: typeof parsed.dimWeekends === "boolean" ? parsed.dimWeekends : DEFAULT_CALENDAR_VIEW_PREFERENCES.dimWeekends,
      dimOffHours: typeof parsed.dimOffHours === "boolean" ? parsed.dimOffHours : DEFAULT_CALENDAR_VIEW_PREFERENCES.dimOffHours,
      workdayStartHour,
      workdayEndHour,
      continuousVirtualization: typeof parsed.continuousVirtualization === "boolean" ? parsed.continuousVirtualization : DEFAULT_CALENDAR_VIEW_PREFERENCES.continuousVirtualization,
      continuousOverscanWeeks: clampNumber(parsed.continuousOverscanWeeks, 2, 20, DEFAULT_CALENDAR_VIEW_PREFERENCES.continuousOverscanWeeks),
      alternateContinuousMonths: typeof parsed.alternateContinuousMonths === "boolean" ? parsed.alternateContinuousMonths : DEFAULT_CALENDAR_VIEW_PREFERENCES.alternateContinuousMonths
    };
  } catch {
    return DEFAULT_CALENDAR_VIEW_PREFERENCES;
  }
}

function loadCalendarMode(): CalendarMode {
  if (typeof window === "undefined") return "continuous";
  try {
    const storedMode = window.localStorage.getItem(CALENDAR_MODE_STORAGE_KEY);
    return isCalendarMode(storedMode) ? storedMode : "continuous";
  } catch {
    return "continuous";
  }
}

function saveCalendarMode(mode: CalendarMode): void {
  if (typeof window === "undefined") return;
  try {
    window.localStorage.setItem(CALENDAR_MODE_STORAGE_KEY, mode);
  } catch {
    // Storage can fail in private browsing or locked-down deployments; mode still works for the current page.
  }
}

function isCalendarMode(value: unknown): value is CalendarMode {
  return value === "continuous" || value === "month" || value === "week" || value === "workweek" || value === "day";
}

function normalizeHexColor(value: string | null | undefined): string | null {
  if (!value) return null;
  const trimmed = value.trim();
  return /^#[0-9a-fA-F]{6}$/.test(trimmed) ? trimmed.toLowerCase() : null;
}

function calendarEventColorStyle(color: string | null | undefined): CSSProperties {
  const normalizedColor = normalizeHexColor(color) || DEFAULT_CALENDAR_COLOR;
  return {
    "--calendar-event-color": normalizedColor,
    "--calendar-event-bg": mixHexWithWhite(normalizedColor, 0.88),
    "--calendar-event-border": mixHexWithWhite(normalizedColor, 0.58)
  } as CSSProperties;
}

function mixHexWithWhite(hex: string, whiteRatio: number): string {
  const normalized = normalizeHexColor(hex) || DEFAULT_CALENDAR_COLOR;
  const ratio = Math.min(1, Math.max(0, whiteRatio));
  const red = parseInt(normalized.slice(1, 3), 16);
  const green = parseInt(normalized.slice(3, 5), 16);
  const blue = parseInt(normalized.slice(5, 7), 16);
  return `rgb(${Math.round(red * (1 - ratio) + 255 * ratio)}, ${Math.round(green * (1 - ratio) + 255 * ratio)}, ${Math.round(blue * (1 - ratio) + 255 * ratio)})`;
}

function continuousVirtualWindow(weekCount: number, viewport: ContinuousViewport, overscanWeeks: number): { start: number; end: number; topSpacerHeight: number; bottomSpacerHeight: number } {
  if (weekCount === 0) return { start: 0, end: 0, topSpacerHeight: 0, bottomSpacerHeight: 0 };
  const visibleStart = Math.floor(viewport.scrollTop / CONTINUOUS_WEEK_ROW_HEIGHT);
  const visibleCount = Math.max(1, Math.ceil((viewport.height || CONTINUOUS_WEEK_ROW_HEIGHT * 8) / CONTINUOUS_WEEK_ROW_HEIGHT));
  const start = Math.max(0, visibleStart - overscanWeeks);
  const end = Math.min(weekCount, visibleStart + visibleCount + overscanWeeks);
  return {
    start,
    end,
    topSpacerHeight: start * CONTINUOUS_WEEK_ROW_HEIGHT,
    bottomSpacerHeight: Math.max(0, (weekCount - end) * CONTINUOUS_WEEK_ROW_HEIGHT)
  };
}

function timeGridStyle(columns: string, preferences: CalendarViewPreferences): CSSProperties {
  return {
    gridTemplateColumns: columns,
    "--calendar-work-start-y": `${preferences.workdayStartHour * HOUR_ROW_HEIGHT}px`,
    "--calendar-work-end-y": `${preferences.workdayEndHour * HOUR_ROW_HEIGHT}px`,
    "--calendar-off-hours-opacity": preferences.dimOffHours ? "1" : "0"
  } as CSSProperties;
}

function moveEventToDay(event: CalendarEvent, day: Date): { startAt: Date; endAt: Date | null; allDay: boolean } {
  const start = new Date(event.start_at);
  const end = event.end_at ? new Date(event.end_at) : null;
  if (event.all_day) {
    const durationDays = Math.max(1, Math.round(daysBetweenCount(startOfDay(start), end ? startOfDay(end) : addDays(startOfDay(start), 1))));
    const startAt = startOfDay(day);
    return { startAt, endAt: addDays(startAt, durationDays), allDay: true };
  }
  const startAt = new Date(day.getFullYear(), day.getMonth(), day.getDate(), start.getHours(), start.getMinutes());
  const durationMs = eventDurationMs(event, 60 * 60 * 1000);
  return { startAt, endAt: new Date(startAt.getTime() + durationMs), allDay: false };
}

function moveEventToTime(event: CalendarEvent, day: Date, minuteOfDay: number): { startAt: Date; endAt: Date | null; allDay: boolean } {
  const snappedMinute = Math.min(23 * 60 + 45, Math.max(0, snapMinute(minuteOfDay)));
  const startAt = dateAtMinuteOfDay(day, snappedMinute);
  const durationMs = event.all_day ? 60 * 60 * 1000 : eventDurationMs(event, 60 * 60 * 1000);
  return { startAt, endAt: new Date(startAt.getTime() + durationMs), allDay: false };
}

function resizeEventToTime(event: CalendarEvent, edge: CalendarResizeEdge, day: Date, minuteOfDay: number): { startAt: Date; endAt: Date | null; allDay: boolean } {
  const target = dateAtMinuteOfDay(day, Math.min(23 * 60 + 45, Math.max(0, snapMinute(minuteOfDay))));
  const currentStart = new Date(event.start_at);
  const fallbackEnd = addMinutes(currentStart, 30);
  const currentEnd = event.end_at && new Date(event.end_at) > currentStart ? new Date(event.end_at) : fallbackEnd;
  const minimumDurationMs = 15 * 60000;
  if (edge === "start") {
    const latestStart = new Date(currentEnd.getTime() - minimumDurationMs);
    return { startAt: target < latestStart ? target : latestStart, endAt: currentEnd, allDay: false };
  }
  const earliestEnd = new Date(currentStart.getTime() + minimumDurationMs);
  return { startAt: currentStart, endAt: target > earliestEnd ? target : earliestEnd, allDay: false };
}

function eventDurationMs(event: CalendarEvent, fallback: number): number {
  if (!event.end_at) return fallback;
  const start = new Date(event.start_at);
  const end = new Date(event.end_at);
  return Math.max(30 * 60 * 1000, end.getTime() - start.getTime());
}

function dropMinuteOfDay(event: ReactDragEvent<HTMLElement>): number {
  const rect = event.currentTarget.getBoundingClientRect();
  const offset = Math.min(Math.max(event.clientY - rect.top, 0), HOUR_ROW_HEIGHT * 24);
  return Math.floor(offset / HOUR_ROW_HEIGHT * 60);
}

function dropSlotMinuteOfDay(event: ReactDragEvent<HTMLElement>): number {
  return Math.min(23 * 60 + 45, Math.max(0, snapMinute(dropMinuteOfDay(event))));
}

function dateAtMinuteOfDay(day: Date, minuteOfDay: number): Date {
  return new Date(day.getFullYear(), day.getMonth(), day.getDate(), Math.floor(minuteOfDay / 60), minuteOfDay % 60);
}

function snapMinute(value: number): number {
  return Math.round(value / 15) * 15;
}

function minuteOfDayLabel(value: number): string {
  const minute = Math.min(23 * 60 + 45, Math.max(0, value));
  return `${String(Math.floor(minute / 60)).padStart(2, "0")}:${String(minute % 60).padStart(2, "0")}`;
}

function isWeekend(value: Date): boolean {
  const day = value.getDay();
  return day === 0 || day === 6;
}

function daysBetweenCount(start: Date, end: Date): number {
  return Math.round((startOfDay(end).getTime() - startOfDay(start).getTime()) / 86400000);
}

function addMinutesToTime(time: string, minutes: number): string {
  const [hourPart, minutePart] = time.split(":");
  const total = Math.min(23 * 60 + 59, Math.max(0, Number(hourPart || 0) * 60 + Number(minutePart || 0) + minutes));
  return `${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
}

function clampNumber(value: unknown, min: number, max: number, fallback: number): number {
  if (typeof value !== "number" || Number.isNaN(value)) return fallback;
  return Math.min(max, Math.max(min, Math.round(value)));
}

type TimedEventSegment = {
  event: CalendarEvent;
  start: Date;
  end: Date;
  top: number;
  height: number;
};

type TimedEventLayoutItem = TimedEventSegment & {
  column: number;
  columns: number;
};

type TimedOverflowLayoutItem = {
  key: string;
  top: number;
  column: number;
  columns: number;
  events: CalendarEvent[];
};

function layoutTimedEventsForDay(events: CalendarEvent[], day: Date): { items: TimedEventLayoutItem[]; overflows: TimedOverflowLayoutItem[] } {
  const segments = timedSegmentsForDay(events, day);
  const clusters = clusterTimedSegments(segments);
  const items: TimedEventLayoutItem[] = [];
  const overflows: TimedOverflowLayoutItem[] = [];

  for (const cluster of clusters) {
    const columnEnds: Date[] = [];
    const assigned = cluster.map((segment) => {
      let column = columnEnds.findIndex((end) => end <= segment.start);
      if (column === -1) column = columnEnds.length;
      columnEnds[column] = segment.end;
      return { ...segment, column };
    });
    const totalColumns = Math.max(1, columnEnds.length);
    const visibleColumns = totalColumns > MAX_TIMED_EVENT_COLUMNS ? MAX_TIMED_EVENT_COLUMNS : totalColumns;
    const overflowColumn = MAX_TIMED_EVENT_COLUMNS - 1;
    const hidden = totalColumns > MAX_TIMED_EVENT_COLUMNS ? assigned.filter((item) => item.column >= overflowColumn) : [];

    for (const item of assigned) {
      if (hidden.includes(item)) continue;
      items.push({ ...item, columns: visibleColumns });
    }
    if (hidden.length > 0) {
      overflows.push({
        key: `${dayKey(day)}-${hidden[0].event.id}-overflow`,
        top: Math.min(...hidden.map((item) => item.top)),
        column: overflowColumn,
        columns: MAX_TIMED_EVENT_COLUMNS,
        events: hidden.map((item) => item.event)
      });
    }
  }

  return { items, overflows };
}

function timedSegmentsForDay(events: CalendarEvent[], day: Date): TimedEventSegment[] {
  const dayStart = startOfDay(day);
  const dayEnd = addDays(dayStart, 1);
  return events
    .filter((event) => !event.all_day)
    .map((event): TimedEventSegment | null => {
      const eventStart = new Date(event.start_at);
      const rawEventEnd = event.end_at ? new Date(event.end_at) : addMinutes(eventStart, 30);
      const eventEnd = rawEventEnd > eventStart ? rawEventEnd : addMinutes(eventStart, 30);
      const start = eventStart < dayStart ? dayStart : eventStart;
      const end = eventEnd > dayEnd ? dayEnd : eventEnd;
      if (end <= start) return null;
      const startMinutes = minutesBetween(dayStart, start);
      const durationMinutes = minutesBetween(start, end);
      return {
        event,
        start,
        end,
        top: Math.max(0, startMinutes / 60 * HOUR_ROW_HEIGHT),
        height: Math.max(26, durationMinutes / 60 * HOUR_ROW_HEIGHT)
      };
    })
    .filter((segment): segment is TimedEventSegment => segment !== null)
    .sort((left, right) => left.start.getTime() - right.start.getTime() || right.end.getTime() - left.end.getTime());
}

function clusterTimedSegments(segments: TimedEventSegment[]): TimedEventSegment[][] {
  const clusters: TimedEventSegment[][] = [];
  let current: TimedEventSegment[] = [];
  let currentEnd: Date | null = null;
  for (const segment of segments) {
    if (!current.length || (currentEnd && segment.start < currentEnd)) {
      current.push(segment);
      if (!currentEnd || segment.end.getTime() > currentEnd.getTime()) currentEnd = segment.end;
      continue;
    }
    clusters.push(current);
    current = [segment];
    currentEnd = segment.end;
  }
  if (current.length) clusters.push(current);
  return clusters;
}

function minutesBetween(start: Date, end: Date): number {
  return Math.max(0, (end.getTime() - start.getTime()) / 60000);
}

function timedEventStyle(item: TimedEventLayoutItem, color?: string | null): CSSProperties {
  return {
    top: `${item.top}px`,
    height: `${item.height}px`,
    left: `calc(${item.column * 100 / item.columns}% + 4px)`,
    width: `calc(${100 / item.columns}% - 8px)`,
    ...calendarEventColorStyle(color)
  };
}

function timedOverflowStyle(item: TimedOverflowLayoutItem): CSSProperties {
  return {
    top: `${item.top}px`,
    left: `calc(${item.column * 100 / item.columns}% + 4px)`,
    width: `calc(${100 / item.columns}% - 8px)`
  };
}

function monthYearLabel(value: Date): string {
  return new Intl.DateTimeFormat(undefined, { month: "long", year: "numeric" }).format(value);
}

function dayMonthLabel(value: Date): string {
  return new Intl.DateTimeFormat(undefined, { day: "2-digit", month: "short" }).format(value);
}

function dayMonthYearLabel(value: Date): string {
  return new Intl.DateTimeFormat(undefined, { day: "2-digit", month: "short", year: "numeric" }).format(value);
}

function agendaDateLabel(value: Date): string {
  return new Intl.DateTimeFormat(undefined, { weekday: "short", day: "2-digit", month: "short", year: "numeric" }).format(value);
}

function weekdayLong(value: Date): string {
  return new Intl.DateTimeFormat(undefined, { weekday: "short" }).format(value);
}

function timeLabel(value: Date): string {
  return new Intl.DateTimeFormat(undefined, { hour: "2-digit", minute: "2-digit" }).format(value);
}

function errorText(error: unknown): string {
  if (error instanceof Error) return error.message;
  return "Calendar request failed.";
}
