import {
  type ExtractPropTypes,
  type PropType,
  computed,
  inject,
  nextTick,
  provide,
  ref,
  watch,
} from 'vue';
import CalendarCellPopover from '../components/CalendarGrid/CalendarCellPopover.vue';
import {
  type Event,
  type EventConfig,
  createEvent as _createEvent,
} from '../utils/calendar/event';
import { MS_PER_HOUR, roundDate } from '../utils/date/helpers';
import { DateRange, DateRangeContext } from '../utils/date/range';
import { on } from '../utils/helpers';
import type { CalendarDay } from '../utils/page';
import { propsDef as basePropsDef, createCalendar, emitsDef } from './calendar';

type GridState =
  | 'NORMAL'
  | 'CREATE_MONITOR'
  | 'DRAG_MONITOR'
  | 'RESIZE_MONITOR';

export type GridStateEvent =
  | 'GRID_CURSOR_DOWN'
  | 'GRID_CURSOR_DOWN_SHIFT'
  | 'GRID_CURSOR_MOVE'
  | 'GRID_CURSOR_MOVE_SHIFT'
  | 'GRID_CURSOR_UP'
  | 'GRID_CURSOR_UP_SHIFT'
  | 'EVENT_CURSOR_DOWN'
  | 'EVENT_CURSOR_DOWN_SHIFT'
  | 'EVENT_CURSOR_MOVE'
  | 'EVENT_CURSOR_MOVE_SHIFT'
  | 'EVENT_RESIZE_START_CURSOR_DOWN'
  | 'EVENT_RESIZE_START_CURSOR_DOWN_SHIFT'
  | 'EVENT_RESIZE_END_CURSOR_DOWN'
  | 'EVENT_RESIZE_END_CURSOR_DOWN_SHIFT'
  | 'ESCAPE';

export interface Point {
  x: number;
  y: number;
}

export interface DragOffset {
  weekdays: number;
  weeks: number;
  ms: number;
}

export interface ResizeOffset {
  weekdays: number;
  weeks: number;
  ms: number;
}

export interface DragOriginState {
  position: number;
  date: Date;
  day: CalendarDay;
  event: Event;
  eventSelected: boolean;
  ms: number;
}

export interface ResizeOriginState {
  position: number;
  day: CalendarDay;
  event: Event;
  isWeekly: boolean;
  isStart: boolean;
  isNew: boolean;
  ms: number;
}

export interface CreateOriginState {
  position: number;
  date: Date;
  day: CalendarDay;
  isWeekly: boolean;
}

// #region Messages

type MessageType =
  | 'event-create-begin'
  | 'event-create-end'
  | 'event-resize-begin'
  | 'event-resize-update'
  | 'event-resize-end'
  | 'event-move-begin'
  | 'event-move-update'
  | 'event-move-end'
  | 'event-remove';

class Messages {
  static _emit: Function;

  static EventCreateBegin(event: Event) {
    return new CancellableEventMessage(this._emit, 'event-create-begin', event);
  }

  static EventCreateEnd(event: Event) {
    return new EventMessage(this._emit, 'event-create-end', event);
  }

  static EventResizeBegin(event: Event) {
    return new CancellableEventMessage(this._emit, 'event-resize-begin', event);
  }

  static EventResizeUpdate(event: Event, offset: ResizeOffset) {
    return new EventResizeMessage(
      this._emit,
      'event-resize-update',
      event,
      offset,
    );
  }

  static EventResizeEnd(event: Event) {
    return new EventMessage(this._emit, 'event-resize-end', event);
  }

  static EventMoveBegin(event: Event) {
    return new CancellableEventMessage(this._emit, 'event-move-begin', event);
  }

  static EventMoveUpdate(event: Event, offset: DragOffset) {
    return new EventMoveMessage(this._emit, 'event-move-update', event, offset);
  }

  static EventMoveEnd(event: Event) {
    return new EventMessage(this._emit, 'event-move-end', event);
  }

  static EventRemove(event: Event) {
    return new CancellableEventMessage(this._emit, 'event-remove', event);
  }
}

class BaseMessage {
  private emit: Function;
  type: MessageType;

  constructor(emit: Function, type: MessageType) {
    this.emit = emit;
    this.type = type;
  }

  send() {
    this.emit(this.type, this);
    return this;
  }

  async sendAsync() {
    this.emit(this.type, this);
    await nextTick();
    return this;
  }
}

class EventMessage<T extends Event | EventConfig> extends BaseMessage {
  event: T;
  constructor(emit: Function, type: MessageType, event: T) {
    super(emit, type);
    this.event = event;
  }
}

class CancellableEventMessage<
  T extends Event | EventConfig,
> extends EventMessage<T> {
  cancel = false;
}

class EventResizeMessage extends CancellableEventMessage<Event> {
  offset?: ResizeOffset;

  constructor(
    emit: Function,
    type: MessageType,
    event: Event,
    offset?: ResizeOffset,
  ) {
    super(emit, type, event);
    this.offset = offset;
  }
}

class EventMoveMessage extends CancellableEventMessage<Event> {
  offset?: DragOffset;

  constructor(
    emit: Function,
    type: MessageType,
    event: Event,
    offset?: ResizeOffset,
  ) {
    super(emit, type, event);
    this.offset = offset;
  }
}

// #endregion Messages

export const emits = [
  ...emitsDef,
  'day-header-click',
  'event-create-begin',
  'event-create-end',
  'event-resize-begin',
  'event-resize-update',
  'event-resize-end',
  'event-move-begin',
  'event-move-update',
  'event-move-end',
  'event-remove',
];

const SNAP_MINUTES = 15;
const PIXELS_PER_HOUR = 50;
const contextKey = Symbol('__vc_grid_context__');

export const propsDef = {
  ...basePropsDef,
  events: {
    type: Object as PropType<EventConfig[]>,
    default: () => [],
  },
};

export type CalendarGridProps = Readonly<ExtractPropTypes<typeof propsDef>>;

export type CalendarGridContext = ReturnType<typeof createCalendarGrid>;

type IBoundingRect = Pick<Element, 'getBoundingClientRect' | 'contains'>;

export function createCalendarGrid(
  props: CalendarGridProps,
  { emit, slots }: any,
) {
  const calendar = createCalendar(props, { emit, slots });
  const cellPopoverRef = ref<typeof CalendarCellPopover>();
  const dailyGridRef = ref<IBoundingRect | null>(null);
  const weeklyGridRef = ref<IBoundingRect | null>(null);
  const activeGridRef = ref<IBoundingRect | null>(null);
  Messages._emit = emit;

  const { view, isDaily, isMonthly, pages, locale, move, onDayFocusin } =
    calendar;

  const page = computed(() => pages.value[0]);
  const days = computed(() => page.value.viewDays);
  const weeks = computed(() => page.value.viewWeeks);
  const dayColumns = computed(() => {
    if (isDaily.value) return 1;
    return weeks.value[0].days.length;
  });
  const dayRows = computed(() => {
    if (isMonthly.value) return weeks.value.length;
    return 1;
  });

  const snapMinutes = ref(SNAP_MINUTES);
  const snapMs = computed(() => snapMinutes.value * 60 * 10000);
  const pixelsPerHour = ref(PIXELS_PER_HOUR);

  const state = ref<GridState>('NORMAL');
  const fill = ref('light');

  const eventsMap = ref<Record<any, Event>>({});
  const events = computed(() => Object.values(eventsMap.value));
  const detailEvent = ref<Event | null>(null);

  const createOrigin = ref<CreateOriginState | null>(null);

  const resizing = ref(false);
  let resizeOrigin: ResizeOriginState | null = null;

  const dragging = ref(false);
  let dragOrigin: DragOriginState | null = null;

  const isTouch = ref(false);

  const active = computed(() => resizing.value || dragging.value);

  const selectedEvents = computed(() => events.value.filter(e => e.selected));

  const selectedEventsCount = computed(() => selectedEvents.value.length);

  const hasSelectedEvents = computed(() => selectedEventsCount.value > 0);

  const eventsContext = computed(() => {
    const ctx = new DateRangeContext();
    events.value.forEach(evt => {
      ctx.render(evt, evt.range, days.value);
    });
    return ctx;
  });

  const gridStyle = computed(() => {
    return {
      height: `${24 * pixelsPerHour.value}px`,
    };
  });

  function getEventContext() {
    return {
      locale,
      days,
      dayRows,
      dayColumns,
      isDaily,
      isMonthly,
      snapMinutes: snapMinutes.value,
      pixelsPerHour: pixelsPerHour.value,
    };
  }

  // #region Event details

  function showCellPopover(event: Event) {
    setTimeout(() => {
      if (isDaily.value || !cellPopoverRef.value) return;
      cellPopoverRef.value.show(event);
    }, 10);
  }

  function updateCellPopover(event: Event) {
    if (!cellPopoverRef.value) return;
    cellPopoverRef.value.update(event);
  }

  function hideCellPopover() {
    if (isDaily.value || !cellPopoverRef.value) return;
    cellPopoverRef.value.hide();
  }

  function popoverVisible() {
    return !!cellPopoverRef.value && cellPopoverRef.value.isVisible();
  }

  // #endregion Cell details

  // #region Util

  function createEventFromExisting(config: EventConfig, range?: DateRange) {
    const event = events.value.find(e => e.key === config.key);
    const ctx = getEventContext();
    if (event != null) {
      const { selected } = event;
      return _createEvent(
        {
          ...config,
          selected,
        },
        ctx,
      );
    }
    return _createEvent(config, ctx);
  }

  function createNewEvent(date: Date, isWeekly: boolean) {
    const event = _createEvent(
      {
        key: Symbol(),
        start: date,
        end: date,
        allDay: isWeekly,
      },
      getEventContext(),
    );
    const msg = Messages.EventCreateBegin(event).send();
    if (msg.cancel || !msg.event) return;
    eventsMap.value[event.key] = event;
    return event;
  }

  function removeEvent(event: Event) {
    const msg = Messages.EventRemove(event).send();
    if (msg.cancel) return;
    delete eventsMap.value[event.key];

    hideCellPopover();
  }

  function getEventsFromProps() {
    return props.events.reduce((map, config) => {
      map[config.key] = map[config.key] || createEventFromExisting(config);
      return map;
    }, {} as Record<keyof any, Event>);
  }

  function getMsFromPosition(position: number) {
    const hours = Math.max(Math.min(position / pixelsPerHour.value, 24), 0);
    return hours * MS_PER_HOUR;
  }

  function getDateFromPosition(
    position: number,
    day: CalendarDay,
    offsetMs = 0,
    snapMs = 0,
  ) {
    const startTime = day.startDate.getTime();
    const ms = getMsFromPosition(position);
    const date = roundDate(startTime + ms + offsetMs, snapMs);
    return date;
  }

  const getPositionFromMouseEvent = (
    gridEl: IBoundingRect | null,
    event: MouseEvent,
  ): Point => {
    if (gridEl == null) return { x: 0, y: 0 };
    const rect = gridEl.getBoundingClientRect();
    const x = event.clientX - rect.left;
    const y = event.clientY - rect.top;
    return { x, y };
  };

  const getPositionFromTouchEvent = (
    gridEl: IBoundingRect | null,
    event: TouchEvent,
  ): Point => {
    if (gridEl == null) return { x: 0, y: 0 };
    const rect = gridEl.getBoundingClientRect();
    const touch = event.targetTouches[0] || event.changedTouches[0];
    const x = touch.clientX - rect.left;
    const y = touch.clientY - rect.top;
    return { x, y };
  };

  const getPositionFromUIEvent = (
    gridEl: IBoundingRect | null,
    event: UIEvent,
  ): Point => {
    if (event.type.startsWith('touch'))
      return getPositionFromTouchEvent(gridEl, event as TouchEvent);
    return getPositionFromMouseEvent(gridEl, event as MouseEvent);
  };

  const getDayFromPosition = (el: IBoundingRect | null, { x, y }: any) => {
    if (el == null) return days.value[0];
    const rect = el.getBoundingClientRect();
    const dayWidth = rect.width / dayColumns.value;
    const dayHeight = rect.height / dayRows.value;
    const xNorm = Math.max(Math.min(x, rect.width), 0);
    const yNorm = Math.max(Math.min(y, rect.height), 0);
    const xIdx = Math.min(Math.floor(xNorm / dayWidth), dayColumns.value - 1);
    const yIdx = Math.min(Math.floor(yNorm / dayHeight), dayRows.value - 1);
    const idx = xIdx + yIdx * dayColumns.value;
    return days.value[idx];
  };

  // #endregion Util

  // #region Cell Operations

  function forSelectedEvents(fn: (event: Event) => void) {
    selectedEvents.value.forEach(e => fn(e));
  }

  function deselectAllEvents() {
    forSelectedEvents(cell => (cell.selected = false));
  }

  // #endregion Cell Operations

  // #region Resizing

  function startResizingEvents(
    position: number,
    day: CalendarDay,
    event: Event,
    isStart: boolean,
    isNew: boolean,
  ) {
    if (active.value) return;
    resizing.value = true;
    event.selected = true;
    const isWeekly = activeGridRef.value === weeklyGridRef.value;
    const ms = getMsFromPosition(position);
    resizeOrigin = {
      position,
      day,
      event,
      isWeekly,
      isStart,
      isNew,
      ms,
    };
    forSelectedEvents(event => {
      const msg = Messages.EventResizeBegin(event).send();
      if (msg.cancel) return;
      event.startResize(day, isStart);
    });
  }

  function updateResizingEvents(position: number, day: CalendarDay) {
    if (!resizing.value || !resizeOrigin) return;
    const offset: ResizeOffset = { weeks: 0, weekdays: 0, ms: 0 };
    if (resizeOrigin.isWeekly) {
      offset.weeks = day.weekPosition - resizeOrigin.day.weekPosition;
      offset.weekdays = day.weekdayPosition - resizeOrigin.day.weekdayPosition;
    } else {
      offset.ms = getMsFromPosition(position) - resizeOrigin.ms;
    }
    forSelectedEvents(event => {
      const msg = Messages.EventResizeUpdate(event, offset).send();
      if (msg.cancel) return;
      event.updateResize(offset);
    });
  }

  function stopResizingEvents() {
    if (!resizing.value || !resizeOrigin) return;
    forSelectedEvents(event => {
      Messages.EventResizeEnd(event);
      if (resizeOrigin!.isNew && event === resizeOrigin!.event) {
        Messages.EventCreateEnd(event).send();
        showCellPopover(event);
      }
      event.stopResize();
    });
    resizing.value = false;
    resizeOrigin = null;
  }

  // #endregion Resizing

  // #region Dragging

  function startDraggingEvents(
    position: number,
    day: CalendarDay,
    event: Event,
  ) {
    if (active.value) return;
    dragging.value = true;
    const date = getDateFromPosition(position, day, 0, 0);
    const eventSelected = event.selected;
    event.selected = true;
    const ms = getMsFromPosition(position);
    dragOrigin = {
      position,
      date,
      day,
      event,
      eventSelected,
      ms,
    };
    selectedEvents.value.forEach(event => {
      const msg = Messages.EventMoveBegin(event).send();
      if (msg.cancel) return;
      event.startDrag(day);
    });
  }

  function updateDraggingEvents(position: number, day: CalendarDay) {
    if (!dragging.value || !dragOrigin) return;
    const offset = {
      weeks: day.weekPosition - dragOrigin.day.weekPosition,
      weekdays: day.weekdayPosition - dragOrigin.day.weekdayPosition,
      ms: getMsFromPosition(position) - dragOrigin.ms,
    };
    forSelectedEvents(event => {
      const msg = Messages.EventMoveUpdate(event, offset).send();
      if (msg.cancel) return;
      event.updateDrag(offset);
    });
  }

  function stopDraggingEvents() {
    if (!dragging.value || !dragOrigin) return;
    dragging.value = false;
    dragOrigin = null;
    forSelectedEvents(event => {
      Messages.EventMoveEnd(event).send();
      event.stopDrag();
    });
  }

  // #endregion Dragging

  // #region Watchers

  function refreshEventsFromProps() {
    eventsMap.value = getEventsFromProps();
  }

  watch(
    () => props.events,
    () => {
      refreshEventsFromProps();
    },
    {
      deep: true,
    },
  );

  watch([view], () => {
    deselectAllEvents();
  });

  // #endregion Watchers

  // #region State management

  function handleNormalEvent(
    gse: GridStateEvent,
    day: CalendarDay,
    position: number,
    evt: Event | undefined,
  ) {
    switch (gse) {
      case 'GRID_CURSOR_DOWN':
      case 'GRID_CURSOR_DOWN_SHIFT': {
        createOrigin.value = {
          isWeekly: activeGridRef.value === weeklyGridRef.value,
          date: getDateFromPosition(position, day),
          position,
          day,
        };
        state.value = 'CREATE_MONITOR';
        break;
      }
      case 'EVENT_CURSOR_DOWN': {
        if (!evt) return;
        if (!evt.selected) deselectAllEvents();
        startDraggingEvents(position, day, evt);
        state.value = 'DRAG_MONITOR';
        break;
      }
      case 'EVENT_CURSOR_DOWN_SHIFT': {
        if (!evt) return;
        startDraggingEvents(position, day, evt);
        state.value = 'DRAG_MONITOR';
        break;
      }
      case 'EVENT_RESIZE_START_CURSOR_DOWN': {
        if (!evt) return;
        if (!evt.selected) deselectAllEvents();
        startResizingEvents(position, day, evt, true, false);
        state.value = 'RESIZE_MONITOR';
        break;
      }
      case 'EVENT_RESIZE_START_CURSOR_DOWN_SHIFT': {
        if (!evt) return;
        startResizingEvents(position, day, evt, true, false);
        state.value = 'RESIZE_MONITOR';
        break;
      }
      case 'EVENT_RESIZE_END_CURSOR_DOWN': {
        if (!evt) return;
        if (!evt.selected) deselectAllEvents();
        startResizingEvents(position, day, evt, false, false);
        state.value = 'RESIZE_MONITOR';
        break;
      }
      case 'EVENT_RESIZE_END_CURSOR_DOWN_SHIFT': {
        if (!evt) return;
        startResizingEvents(position, day, evt, false, false);
        state.value = 'RESIZE_MONITOR';
        break;
      }
    }
  }

  function handleCreateMonitorEvent(gse: GridStateEvent, day: CalendarDay) {
    if (!createOrigin.value) return;

    switch (gse) {
      case 'ESCAPE': {
        deselectAllEvents();
        break;
      }
      case 'GRID_CURSOR_UP':
      case 'GRID_CURSOR_UP_SHIFT': {
        deselectAllEvents();
        if (!popoverVisible()) {
          const { position, isWeekly } = createOrigin.value;
          const date = getDateFromPosition(position, day);
          const evt = createNewEvent(date, isWeekly);
          if (evt) {
            evt.selected = true;
            Messages.EventCreateEnd(evt).send();
            showCellPopover(evt);
          }
        }
        state.value = 'NORMAL';
        break;
      }
      case 'EVENT_CURSOR_MOVE':
      case 'EVENT_CURSOR_MOVE_SHIFT':
      case 'GRID_CURSOR_MOVE':
      case 'GRID_CURSOR_MOVE_SHIFT': {
        // if (isTouch.value || isMonthly.value) {
        // if (isTouch.value) {
        //   state.value = 'NORMAL';
        //   return;
        // }
        deselectAllEvents();
        const { position, isWeekly } = createOrigin.value;
        const date = getDateFromPosition(position, day);
        const evt = createNewEvent(date, isWeekly);
        if (evt) {
          startResizingEvents(position, day, evt, false, true);
          updateResizingEvents(position, day);
          state.value = 'RESIZE_MONITOR';
        }
        break;
      }
    }
  }

  function handleResizeMonitorEvent(
    event: GridStateEvent,
    position: number,
    day: CalendarDay,
  ) {
    if (!resizeOrigin) return;
    switch (event) {
      case 'EVENT_CURSOR_MOVE':
      case 'EVENT_CURSOR_MOVE_SHIFT':
      case 'GRID_CURSOR_MOVE':
      case 'GRID_CURSOR_MOVE_SHIFT': {
        updateResizingEvents(position, day);
        if (!resizeOrigin.isNew) {
          updateCellPopover(resizeOrigin.event);
        }
        break;
      }
      case 'GRID_CURSOR_UP': {
        if (position === resizeOrigin.position) {
          deselectAllEvents();
          resizeOrigin.event.selected = true;
        }
        stopResizingEvents();
        state.value = 'NORMAL';
        break;
      }
      case 'GRID_CURSOR_UP_SHIFT': {
        stopResizingEvents();
        state.value = 'NORMAL';
        break;
      }
    }
  }

  function handleDragMonitorEvent(
    event: GridStateEvent,
    day: CalendarDay,
    position: number,
  ) {
    if (!dragOrigin) return;
    switch (event) {
      case 'GRID_CURSOR_MOVE':
      case 'GRID_CURSOR_MOVE_SHIFT': {
        updateDraggingEvents(position, day);
        updateCellPopover(dragOrigin.event);
        break;
      }
      case 'GRID_CURSOR_UP': {
        const origin = dragOrigin;
        stopDraggingEvents();
        if (position === origin.position) {
          deselectAllEvents();
          origin.event.selected = true;
          showCellPopover(origin.event);
        }
        state.value = 'NORMAL';
        break;
      }
      case 'GRID_CURSOR_UP_SHIFT': {
        stopDraggingEvents();
        state.value = 'NORMAL';
        break;
      }
    }
  }

  function updateState(
    gse: GridStateEvent,
    day: CalendarDay,
    position: number,
    evt: Event | undefined = undefined,
  ) {
    switch (state.value) {
      case 'NORMAL': {
        handleNormalEvent(gse, day, position, evt);
        break;
      }
      case 'CREATE_MONITOR': {
        handleCreateMonitorEvent(gse, day);
        break;
      }
      case 'RESIZE_MONITOR': {
        handleResizeMonitorEvent(gse, position, day);
        break;
      }
      case 'DRAG_MONITOR': {
        handleDragMonitorEvent(gse, day, position);
        break;
      }
    }
  }

  const setActiveGrid = (event: MouseEvent | TouchEvent) => {
    activeGridRef.value =
      [dailyGridRef, weeklyGridRef].find(
        ref => ref.value && ref.value.contains(event.currentTarget as Node),
      )?.value ?? null;
  };

  const handleEvent = (
    stateEvent: GridStateEvent,
    event: MouseEvent | TouchEvent | KeyboardEvent,
    evt: Event | undefined = undefined,
  ) => {
    if (!activeGridRef.value) return;
    if (event.type.startsWith('touch')) {
      isTouch.value = true;
    } else if (isTouch.value) {
      return;
    }
    const eventName = (
      event.shiftKey ? `${stateEvent}_SHIFT` : stateEvent
    ) as GridStateEvent;
    const position = getPositionFromUIEvent(activeGridRef.value, event);
    const day = getDayFromPosition(activeGridRef.value, position);
    updateState(eventName, day, position.y, evt);
    if (stateEvent === 'GRID_CURSOR_DOWN') {
      onDayFocusin(day, null);
    }
  };

  const startMonitoringGridMove = () => {
    const offMove = on(window, 'mousemove', event => {
      handleEvent('GRID_CURSOR_MOVE', event as MouseEvent);
    });
    const offUp = on(window, 'mouseup', event => {
      handleEvent('GRID_CURSOR_UP', event as MouseEvent);
      offMove();
      offUp();
    });
  };

  // #endregion State management

  refreshEventsFromProps();

  const context = {
    ...calendar,
    dailyGridRef,
    weeklyGridRef,
    cellPopoverRef,
    dayColumns,
    dayRows,
    snapMinutes,
    snapMs,
    pixelsPerHour,
    isTouch,
    events,
    eventsMap,
    selectedEvents,
    hasSelectedEvents,
    eventsContext,
    detailEvent,
    resizing,
    dragging,
    gridStyle,
    fill,
    page,
    days,
    weeks,
    // Methods
    removeEvent,
    // Event handlers
    onDayNumberClick(day: CalendarDay) {
      emit('day-header-click', day);
      move(day, { view: 'daily' });
    },
    onGridEscapeKeydown() {
      updateState('ESCAPE', days.value[0], 0);
    },
    // Mouse event handlers
    onGridMouseDown(event: MouseEvent) {
      setActiveGrid(event);
      handleEvent('GRID_CURSOR_DOWN', event);
      startMonitoringGridMove();
    },
    onEventMouseDown(event: MouseEvent, evt: Event) {
      setActiveGrid(event);
      handleEvent('EVENT_CURSOR_DOWN', event, evt);
    },
    onEventResizeStartMouseDown(event: MouseEvent, evt: Event) {
      setActiveGrid(event);
      handleEvent('EVENT_RESIZE_START_CURSOR_DOWN', event, evt);
    },
    onEventResizeEndMouseDown(event: MouseEvent, evt: Event) {
      setActiveGrid(event);
      handleEvent('EVENT_RESIZE_END_CURSOR_DOWN', event, evt);
    },
    // Touch event handlers
    onGridTouchStart(event: TouchEvent) {
      setActiveGrid(event);
      handleEvent('GRID_CURSOR_DOWN', event);
    },
    onGridTouchMove(event: TouchEvent) {
      handleEvent('GRID_CURSOR_MOVE', event);
    },
    onGridTouchEnd(event: TouchEvent) {
      handleEvent('GRID_CURSOR_UP', event);
    },
    onEventTouchStart(event: TouchEvent, evt: Event) {
      setActiveGrid(event);
      handleEvent('EVENT_CURSOR_DOWN', event, evt);
    },
    onEventTouchMove(event: TouchEvent, evt: Event) {
      handleEvent('GRID_CURSOR_MOVE', event, evt);
    },
    onEventTouchEnd(event: TouchEvent, evt: Event) {
      handleEvent('GRID_CURSOR_UP', event, evt);
    },
    onEventResizeStartTouchStart(event: TouchEvent, evt: Event) {
      setActiveGrid(event);
      handleEvent('EVENT_RESIZE_START_CURSOR_DOWN', event, evt);
    },
    onEventResizeEndTouchStart(event: TouchEvent, evt: Event) {
      setActiveGrid(event);
      handleEvent('EVENT_RESIZE_END_CURSOR_DOWN', event, evt);
    },
  };
  provide(contextKey, context);

  return context;
}

export function useCalendarGrid() {
  const context = inject<CalendarGridContext>(contextKey);
  if (!context) {
    throw new Error(
      'Calendar context missing. Please verify this component is nested within a valid context provider.',
    );
  }
  return context;
}
