"use client"

/**
 * CalendarView — shared month/week/day event grid.
 *
 * Wraps react-big-calendar with a date-fns localizer pre-wired and a
 * simplified event shape so consumers don't have to reach for raw RBC types.
 *
 * IMPORTANT: react-big-calendar ships its own CSS. Consumers must import
 * `react-big-calendar/lib/css/react-big-calendar.css` once at the app level
 * (e.g. in _app.tsx / layout.tsx). This package does NOT auto-inject CSS so
 * consumers can layer Tailwind overrides predictably.
 *
 * lifecycle:calendar-ui (raise-simpli-k4u)
 */

import * as React from "react"
import {
  Calendar,
  dateFnsLocalizer,
  Views,
  type View,
  type SlotInfo,
} from "react-big-calendar"
import { format, parse, startOfWeek, getDay } from "date-fns"
import { enUS } from "date-fns/locale"

const locales = { "en-US": enUS }

const localizer = dateFnsLocalizer({
  format,
  parse,
  startOfWeek: () => startOfWeek(new Date(), { weekStartsOn: 0 }),
  getDay,
  locales,
})

/** Domain-friendly event shape callers pass in. */
export interface CalendarEvent<T = unknown> {
  /** Stable identifier — passed through to onEventClick payload */
  id: string | number
  /** Event title shown on the grid */
  title: string
  /** Event start (Date or ISO string) */
  start: Date | string
  /** Event end. If omitted, defaults to start + 30min */
  end?: Date | string
  /** Whether to render as all-day across the top of the day */
  allDay?: boolean
  /** Optional pass-through payload (the original ScheduledMeeting / etc.) */
  resource?: T
}

interface InternalRBCEvent {
  id: string | number
  title: string
  start: Date
  end: Date
  allDay: boolean
  resource: unknown
}

const DEFAULT_DURATION_MS = 30 * 60 * 1000

function toDate(value: Date | string): Date {
  return value instanceof Date ? value : new Date(value)
}

function normalizeEvent(e: CalendarEvent): InternalRBCEvent {
  const start = toDate(e.start)
  const end = e.end ? toDate(e.end) : new Date(start.getTime() + DEFAULT_DURATION_MS)
  return {
    id: e.id,
    title: e.title,
    start,
    end,
    allDay: e.allDay ?? false,
    resource: e.resource,
  }
}

export type CalendarViewMode = "month" | "week" | "day" | "agenda"

const VIEW_BY_MODE: Record<CalendarViewMode, View> = {
  month: Views.MONTH,
  week: Views.WEEK,
  day: Views.DAY,
  agenda: Views.AGENDA,
}

export interface CalendarViewProps<T = unknown> {
  /** Events to render on the grid */
  events: CalendarEvent<T>[]
  /** Currently navigated date (controlled) */
  date?: Date
  /** Default date if uncontrolled */
  defaultDate?: Date
  /** Active view (controlled) */
  view?: CalendarViewMode
  /** Default view if uncontrolled */
  defaultView?: CalendarViewMode
  /** Allowed views the toolbar can switch between */
  views?: CalendarViewMode[]
  /** Fires when the user clicks an event. Receives the original event passed in. */
  onEventClick?: (event: CalendarEvent<T>) => void
  /** Fires when the user clicks an empty slot (or drags to select a range). */
  onSlotClick?: (slot: { start: Date; end: Date; action: "click" | "select" }) => void
  /** Fires when the user navigates to a new date. */
  onNavigate?: (date: Date) => void
  /** Fires when the user changes view. */
  onViewChange?: (view: CalendarViewMode) => void
  /** Allow drag-to-select empty time slots (defaults true). */
  selectable?: boolean
  /** Min height for the grid (default 600px). */
  minHeight?: number
  /** className passed to the wrapping div for Tailwind overrides. */
  className?: string
  /**
   * Per-event styling. Receives the original CalendarEvent (with resource)
   * and returns optional className / inline style applied to the rendered
   * event block. Useful for color-coding by source (e.g. real Outlook event
   * vs locally-scheduled meeting).
   */
  eventPropGetter?: (event: CalendarEvent<T>) => {
    className?: string
    style?: React.CSSProperties
  }
  /**
   * When the month grid runs out of vertical space, react-big-calendar
   * collapses the overflow into a "+N more" link. Set true (default) to
   * open the day's full event list as a popover when that link is clicked.
   */
  popup?: boolean
}

/**
 * Shared calendar grid. Pre-wired with date-fns localizer.
 *
 * @example
 * import "react-big-calendar/lib/css/react-big-calendar.css"
 * import { CalendarView } from "@startsimpli/ui"
 *
 * <CalendarView
 *   events={meetings.map(m => ({
 *     id: m.id,
 *     title: m.title,
 *     start: m.scheduled_at,
 *     end: addMinutes(new Date(m.scheduled_at), m.duration_minutes),
 *     resource: m,
 *   }))}
 *   onEventClick={e => openMeetingDialog(e.resource)}
 *   onSlotClick={s => openCreateMeetingDialog(s.start)}
 * />
 */
export function CalendarView<T = unknown>({
  events,
  date,
  defaultDate,
  view,
  defaultView,
  views,
  onEventClick,
  onSlotClick,
  onNavigate,
  onViewChange,
  selectable = true,
  minHeight = 600,
  className,
  eventPropGetter,
  popup = true,
}: CalendarViewProps<T>) {
  const normalized = React.useMemo(() => events.map(normalizeEvent), [events])

  // Map our public CalendarEvent[] back from the internal event RBC hands us
  // on click — keeps the consumer API clean (they only see what they passed).
  const eventByInternalId = React.useMemo(() => {
    const map = new Map<string | number, CalendarEvent<T>>()
    for (const e of events) map.set(e.id, e)
    return map
  }, [events])

  const handleSelectEvent = React.useCallback(
    (rbcEvent: object) => {
      const id = (rbcEvent as InternalRBCEvent).id
      const original = eventByInternalId.get(id)
      if (original && onEventClick) onEventClick(original)
    },
    [eventByInternalId, onEventClick],
  )

  const handleSelectSlot = React.useCallback(
    (slot: SlotInfo) => {
      if (!onSlotClick) return
      onSlotClick({
        start: slot.start as Date,
        end: slot.end as Date,
        action: slot.action === "select" ? "select" : "click",
      })
    },
    [onSlotClick],
  )

  const handleViewChange = React.useCallback(
    (v: View) => {
      if (!onViewChange) return
      const mode = (Object.entries(VIEW_BY_MODE).find(([, val]) => val === v)?.[0] ?? "month") as CalendarViewMode
      onViewChange(mode)
    },
    [onViewChange],
  )

  const allowedViews = React.useMemo<View[]>(
    () => (views ?? ["month", "week", "day", "agenda"]).map(v => VIEW_BY_MODE[v]),
    [views],
  )

  const rbcEventPropGetter = React.useCallback(
    (rbcEvent: object) => {
      if (!eventPropGetter) return {}
      const id = (rbcEvent as InternalRBCEvent).id
      const original = eventByInternalId.get(id)
      if (!original) return {}
      return eventPropGetter(original)
    },
    [eventPropGetter, eventByInternalId],
  )

  return (
    <div className={className} style={{ minHeight }}>
      <Calendar
        localizer={localizer}
        events={normalized}
        startAccessor="start"
        endAccessor="end"
        titleAccessor="title"
        allDayAccessor="allDay"
        date={date}
        defaultDate={defaultDate}
        view={view ? VIEW_BY_MODE[view] : undefined}
        defaultView={defaultView ? VIEW_BY_MODE[defaultView] : undefined}
        views={allowedViews}
        onSelectEvent={handleSelectEvent}
        onSelectSlot={handleSelectSlot}
        onNavigate={onNavigate}
        onView={handleViewChange}
        selectable={selectable}
        popup={popup}
        eventPropGetter={eventPropGetter ? rbcEventPropGetter : undefined}
        style={{ height: "100%", minHeight }}
      />
    </div>
  )
}
