import {
  useEffect,
  useMemo,
  useRef,
  type DragEvent as ReactDragEvent,
} from "react";

import { i18nMessage } from "@govoplan/core-webui";

import type { CalendarEvent } from "../../api/calendar";
import {
  HOUR_ROW_HEIGHT,
  INITIAL_SCROLL_HOUR,
  calendarEventColorStyle,
  chunk,
  continuousVirtualWindow,
  dayKey,
  dayMonthLabel,
  dropSlotMinuteOfDay,
  eventTimeLabel,
  isWeekend,
  layoutTimedEventsForDay,
  minuteOfDayLabel,
  monthYearLabel,
  sameDay,
  sameMonth,
  timedEventStyle,
  timedOverflowStyle,
  timeGridStyle,
  weekdayLong,
  type CalendarDropTarget,
  type CalendarMode,
  type CalendarResizeEdge,
  type CalendarTimeDropTarget,
  type CalendarViewPreferences,
  type ContinuousViewport,
} from "./calendarViewModel";

type CalendarViewCallbacks = {
  onEventSelect: (event: CalendarEvent) => void;
  onEventHover: (eventId: string) => void;
  onEventDragStart: (
    dragEvent: ReactDragEvent<HTMLElement>,
    event: CalendarEvent,
  ) => void;
  onEventDragEnd: () => void;
  onEventDragOverDay: (
    dragEvent: ReactDragEvent<HTMLElement>,
    day: Date,
  ) => void;
  onDropTargetLeave: (
    dragEvent: ReactDragEvent<HTMLElement>,
  ) => void;
  onEventDropOnDay: (
    dropEvent: ReactDragEvent<HTMLElement>,
    day: Date,
  ) => void;
};

type CalendarWeekRowsProps = CalendarViewCallbacks & {
  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;
};

export function CalendarWeekRows({
  days,
  eventsByDay,
  calendarColorById,
  focusDate,
  variant,
  preferences,
  canWrite,
  draggingEventId,
  hoveredEventId,
  dropTarget,
  viewport,
  onEventSelect,
  onEventHover,
  onEventDragStart,
  onEventDragEnd,
  onEventDragOverDay,
  onDropTargetLeave,
  onEventDropOnDay,
}: CalendarWeekRowsProps) {
  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,
  );
  const eventLimit = variant === "month" ? 5 : 3;

  return (
    <div className={`calendar-week-rows is-${variant}`}>
      <div className="calendar-weekday-header">
        {[
          "i18n:govoplan-calendar.mon.24b2a099",
          "i18n:govoplan-calendar.tue.529541bb",
          "i18n:govoplan-calendar.wed.23408b19",
          "i18n:govoplan-calendar.thu.3593ccd9",
          "i18n:govoplan-calendar.fri.bbd6e32e",
          "i18n:govoplan-calendar.sat.6b782d41",
          "i18n:govoplan-calendar.sun.48c98cab",
        ].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, eventLimit).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 > eventLimit && (
                    <span className="calendar-more-events">
                      +{dayEvents.length - eventLimit}
                    </span>
                  )}
                </div>
              </section>
            );
          })}
        </div>
      ))}
      {virtualWindow.bottomSpacerHeight > 0 && (
        <div
          className="calendar-week-spacer"
          style={{ height: virtualWindow.bottomSpacerHeight }}
          aria-hidden="true"
        />
      )}
    </div>
  );
}

type CalendarTimeGridProps = CalendarViewCallbacks & {
  days: Date[];
  eventsByDay: Map<string, CalendarEvent[]>;
  calendarColorById: Map<string, string>;
  mode: CalendarMode;
  preferences: CalendarViewPreferences;
  canWrite: boolean;
  draggingEventId: string;
  hoveredEventId: string;
  dropTarget: CalendarDropTarget;
  onEventResizeDragStart: (
    dragEvent: ReactDragEvent<HTMLElement>,
    event: CalendarEvent,
    edge: CalendarResizeEdge,
  ) => void;
  onEventDragOverTime: (
    dragEvent: ReactDragEvent<HTMLElement>,
    day: Date,
  ) => void;
  onEventDropOnTime: (
    dropEvent: ReactDragEvent<HTMLElement>,
    day: Date,
    minuteOfDay: number,
  ) => void;
};

export function CalendarTimeGrid({
  days,
  eventsByDay,
  calendarColorById,
  mode,
  preferences,
  canWrite,
  draggingEventId,
  hoveredEventId,
  dropTarget,
  onEventSelect,
  onEventHover,
  onEventDragStart,
  onEventResizeDragStart,
  onEventDragEnd,
  onEventDragOverDay,
  onEventDragOverTime,
  onDropTargetLeave,
  onEventDropOnDay,
  onEventDropOnTime,
}: CalendarTimeGridProps) {
  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">
            i18n:govoplan-calendar.all_day.11457433
          </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>
  );
}

type TimedDayColumnProps = {
  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>,
    event: CalendarEvent,
  ) => void;
  onEventResizeDragStart: (
    dragEvent: ReactDragEvent<HTMLElement>,
    event: 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;
};

function TimedDayColumn({
  day,
  events,
  calendarColorById,
  dimWeekend,
  canWrite,
  draggingEventId,
  hoveredEventId,
  dropTarget,
  onEventSelect,
  onEventHover,
  onEventDragStart,
  onEventResizeDragStart,
  onEventDragEnd,
  onEventDragOverTime,
  onDropTargetLeave,
  onEventDropOnTime,
}: TimedDayColumnProps) {
  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={i18nMessage(
            "i18n:govoplan-calendar.edit_value.fad75899",
            { value0: item.event.summary },
          )}
        >
          {canWrite && (
            <span
              className="calendar-event-resize-handle is-start"
              draggable
              title="i18n:govoplan-calendar.change_start_time.06eea3d3"
              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={i18nMessage(
              "i18n:govoplan-calendar.edit_value.fad75899",
              { value0: item.event.summary },
            )}
          >
            <EventInlineLabel event={item.event} />
          </button>
          {canWrite && (
            <span
              className="calendar-event-resize-handle is-end"
              draggable
              title="i18n:govoplan-calendar.change_end_time.db66ef4b"
              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>
  );
}

type CalendarEventChipProps = {
  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>,
    event: CalendarEvent,
  ) => void;
  onDragEnd?: () => void;
};

function CalendarEventChip({
  event,
  color,
  compact = false,
  canDrag = false,
  dragging = false,
  linkedHover = false,
  onSelect,
  onHover,
  onDragStart,
  onDragEnd,
}: CalendarEventChipProps) {
  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={i18nMessage(
        "i18n:govoplan-calendar.edit_value.fad75899",
        { value0: event.summary },
      )}
    >
      <EventInlineLabel event={event} />
    </button>
  );
}

export 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>
  );
}
