"use client"

import {
  useEffect,
  useRef,
  useState,
  type MouseEvent,
  type PointerEvent as ReactPointerEvent,
} from "react"
import { addDays, format, isSameDay, isSameMonth, startOfDay } from "date-fns"
import { fr } from "date-fns/locale"
import { AgendaEventChip } from "@/components/agenda/agenda-event-chip"
import type { AnchorRect } from "@/components/agenda/agenda-floating-card"
import { bindAgendaEventDragSession } from "@/lib/agenda/agenda-event-drag-session"
import { viewDays, type WeekStartsOn } from "@/lib/agenda/agenda-date"
import type { AgendaWeekStart } from "@/lib/agenda/agenda-settings-types"
import { eventsOnDay, isMultiDay } from "@/lib/agenda/agenda-events"
import { isPendingEvent } from "@/lib/agenda/agenda-pending-event"
import type { AgendaEvent } from "@/lib/agenda/agenda-types"
import { cn } from "@/lib/utils"

const MAX_CHIPS = 4

function anchorFromEvent(e: MouseEvent<HTMLElement>): AnchorRect {
  const rect = e.currentTarget.getBoundingClientRect()
  return { left: rect.left, top: rect.top, width: rect.width, height: rect.height }
}

function dayIndex(days: Date[], day: Date): number {
  return days.findIndex((d) => isSameDay(d, day))
}

interface MonthDragState {
  startIndex: number
  endIndex: number
  moved: boolean
}

interface MonthEventMoveState {
  event: AgendaEvent
  originIndex: number
  currentIndex: number
  positionChanged: boolean
}

export function AgendaViewMonth({
  date,
  weekStart = "auto",
  weekStartsOn,
  events,
  pendingEvent,
  onCreateRange,
  onEventClick,
  onEventMove,
  onOpenDay,
}: {
  date: Date
  weekStart?: AgendaWeekStart
  weekStartsOn?: WeekStartsOn
  events: AgendaEvent[]
  pendingEvent?: AgendaEvent | null
  onCreateRange: (
    start: Date,
    end: Date,
    allDay: boolean,
    anchor: AnchorRect,
    viaDrag: boolean,
  ) => void
  onEventClick: (event: AgendaEvent, anchor: AnchorRect) => void
  onEventMove?: (event: AgendaEvent, targetStart: Date) => void
  onOpenDay: (day: Date) => void
}) {
  const days = viewDays("month", date, weekStart, weekStartsOn)
  const weeks: Date[][] = []
  for (let i = 0; i < days.length; i += 7) weeks.push(days.slice(i, i + 7))
  const today = new Date()

  const cellRefs = useRef<(HTMLDivElement | null)[]>([])
  const dragRef = useRef<MonthDragState | null>(null)
  const eventMoveRef = useRef<MonthEventMoveState | null>(null)
  const dragSessionCleanupRef = useRef<(() => void) | null>(null)
  const suppressClickRef = useRef(false)
  const [drag, setDrag] = useState<MonthDragState | null>(null)
  const [eventMove, setEventMove] = useState<MonthEventMoveState | null>(null)

  const merged = pendingEvent ? [...events, pendingEvent] : events

  useEffect(
    () => () => {
      dragSessionCleanupRef.current?.()
      dragSessionCleanupRef.current = null
    },
    [],
  )

  const indexFromElement = (el: HTMLElement | null): number | null => {
    if (!el) return null
    const idx = cellRefs.current.findIndex((cell) => cell && cell.contains(el))
    return idx >= 0 ? idx : null
  }

  const indexFromClient = (clientX: number, clientY: number): number | null => {
    const el = document.elementFromPoint(clientX, clientY) as HTMLElement | null
    const cell = el?.closest("[data-agenda-month-cell]") as HTMLElement | null
    return indexFromElement(cell)
  }

  const finishDrag = (anchorEl: HTMLElement | null) => {
    const d = dragRef.current
    dragRef.current = null
    setDrag(null)
    if (!d) return

    const minIdx = Math.min(d.startIndex, d.endIndex)
    const maxIdx = Math.max(d.startIndex, d.endIndex)
    const start = startOfDay(days[minIdx])
    const end = addDays(startOfDay(days[maxIdx]), 1)
    const anchor = anchorEl
      ? anchorFromEvent({ currentTarget: anchorEl } as MouseEvent<HTMLElement>)
      : { left: 0, top: 0, width: 0, height: 0 }

    if (d.moved) {
      onCreateRange(start, end, true, anchor, true)
    } else {
      onCreateRange(start, end, true, anchor, false)
    }
  }

  const handlePointerDown = (dayIndex: number) => (e: ReactPointerEvent<HTMLDivElement>) => {
    if (e.button !== 0 || eventMoveRef.current) return
    if ((e.target as HTMLElement).closest("[data-agenda-event]")) return
    const next: MonthDragState = { startIndex: dayIndex, endIndex: dayIndex, moved: false }
    dragRef.current = next
    setDrag(next)
    e.currentTarget.setPointerCapture(e.pointerId)
  }

  const handlePointerMove = (e: ReactPointerEvent<HTMLDivElement>) => {
    const d = dragRef.current
    if (!d) return
    const idx = indexFromClient(e.clientX, e.clientY)
    if (idx === null || idx === d.endIndex) return
    const next: MonthDragState = { ...d, endIndex: idx, moved: true }
    dragRef.current = next
    setDrag(next)
  }

  const handlePointerUp = (dayIndex: number) => (e: ReactPointerEvent<HTMLDivElement>) => {
    finishDrag(cellRefs.current[dayIndex])
  }

  const handlePointerCancel = () => {
    dragRef.current = null
    setDrag(null)
  }

  const finishEventMove = (didDrag: boolean) => {
    const move = eventMoveRef.current
    eventMoveRef.current = null
    setEventMove(null)
    if (!move || !onEventMove) return

    const positionChanged = move.currentIndex !== move.originIndex
    if (didDrag) {
      suppressClickRef.current = true
    }
    if (!didDrag || !positionChanged) return

    const delta = move.currentIndex - move.originIndex
    const targetStart = addDays(move.event.start, delta)
    onEventMove(move.event, targetStart)
  }

  const updateEventMove = (clientX: number, clientY: number) => {
    const move = eventMoveRef.current
    if (!move) return
    const idx = indexFromClient(clientX, clientY)
    if (idx === null) return

    const positionChanged = idx !== move.originIndex
    const next: MonthEventMoveState = {
      ...move,
      currentIndex: idx,
      positionChanged,
    }
    eventMoveRef.current = next
    setEventMove(next)
  }

  const shouldSuppressEventClick = () => {
    if (!suppressClickRef.current) return false
    suppressClickRef.current = false
    return true
  }

  const startEventMove =
    (event: AgendaEvent, dayIdx: number) =>
    (e: ReactPointerEvent<HTMLElement>) => {
      if (e.button !== 0 || isPendingEvent(event) || !onEventMove) return
      e.stopPropagation()
      e.preventDefault()

      const originIndex = dayIndex(days, event.start)
      const resolvedOrigin = originIndex >= 0 ? originIndex : dayIdx
      const next: MonthEventMoveState = {
        event,
        originIndex: resolvedOrigin,
        currentIndex: resolvedOrigin,
        positionChanged: false,
      }
      eventMoveRef.current = next
      setEventMove(next)

      dragSessionCleanupRef.current?.()
      dragSessionCleanupRef.current = bindAgendaEventDragSession({
        pointerId: e.pointerId,
        startX: e.clientX,
        startY: e.clientY,
        onMove: updateEventMove,
        onFinish: (clientX, clientY, didDrag) => {
          dragSessionCleanupRef.current = null
          if (didDrag) {
            updateEventMove(clientX, clientY)
          }
          finishEventMove(didDrag)
        },
      })
    }

  const dragRange =
    drag && drag.moved
      ? {
          min: Math.min(drag.startIndex, drag.endIndex),
          max: Math.max(drag.startIndex, drag.endIndex),
        }
      : null

  return (
    <div className="flex h-full min-h-0 flex-col overflow-hidden bg-mail-surface">
      <div className="grid shrink-0 grid-cols-7 border-b border-border/60">
        {days.slice(0, 7).map((d) => (
          <div
            key={d.getTime()}
            className="py-1.5 text-center text-[0.7rem] font-medium tracking-wide text-muted-foreground uppercase"
          >
            {format(d, "EEE", { locale: fr }).replace(".", "")}
          </div>
        ))}
      </div>
      <div
        className="grid min-h-0 flex-1 grid-cols-7"
        style={{ gridTemplateRows: `repeat(${weeks.length}, minmax(0, 1fr))` }}
      >
        {weeks.map((week) =>
          week.map((day) => {
            const idx = dayIndex(days, day)
            const dayEvents = eventsOnDay(merged, day).sort((a, b) => {
              const aBanner = a.allDay || isMultiDay(a)
              const bBanner = b.allDay || isMultiDay(b)
              if (isPendingEvent(a)) return 1
              if (isPendingEvent(b)) return -1
              if (aBanner !== bBanner) return aBanner ? -1 : 1
              return a.start.getTime() - b.start.getTime()
            })
            const visible = dayEvents.slice(0, MAX_CHIPS)
            const hidden = dayEvents.length - visible.length
            const isToday = isSameDay(day, today)
            const inMonth = isSameMonth(day, date)
            const inDragRange = dragRange && idx >= dragRange.min && idx <= dragRange.max
            const inEventMoveTarget =
              eventMove?.positionChanged && eventMove.currentIndex === idx

            return (
              <div
                key={day.getTime()}
                ref={(el) => {
                  cellRefs.current[idx] = el
                }}
                role="gridcell"
                data-agenda-month-cell
                className={cn(
                  "relative flex min-h-0 cursor-pointer touch-none flex-col gap-0.5 overflow-hidden border-r border-b border-border/40 px-1 pb-1",
                  !inMonth && "bg-muted/30",
                  inDragRange && "bg-primary/10",
                  inEventMoveTarget && "bg-primary/10 ring-1 ring-inset ring-primary/40",
                )}
                onPointerDown={handlePointerDown(idx)}
                onPointerMove={handlePointerMove}
                onPointerUp={handlePointerUp(idx)}
                onPointerCancel={handlePointerCancel}
              >
                <div className="flex justify-center pt-1">
                  <button
                    type="button"
                    className={cn(
                      "flex h-6 min-w-6 items-center justify-center rounded-full px-1 text-xs hover:bg-mail-nav-hover",
                      isToday && "bg-primary font-semibold text-primary-foreground hover:bg-primary",
                      !inMonth && "text-muted-foreground/60",
                    )}
                    onPointerDown={(e) => e.stopPropagation()}
                    onClick={(e) => {
                      e.stopPropagation()
                      onOpenDay(day)
                    }}
                  >
                    {day.getDate() === 1 && inMonth
                      ? format(day, "d MMM", { locale: fr })
                      : day.getDate()}
                  </button>
                </div>
                {visible.map((event) => {
                  const isDragging =
                    eventMove?.event.key === event.key && eventMove.positionChanged
                  return (
                    <div
                      key={event.key}
                      data-agenda-event
                      className={cn(
                        onEventMove &&
                          !isPendingEvent(event) &&
                          "touch-none cursor-grab active:cursor-grabbing",
                        isDragging && "opacity-40",
                      )}
                      onPointerDown={startEventMove(event, idx)}
                    >
                      <AgendaEventChip
                        event={event}
                        filled={event.allDay || isMultiDay(event)}
                        pending={isPendingEvent(event)}
                        onClick={(e) => {
                          if (shouldSuppressEventClick()) {
                            e.preventDefault()
                            return
                          }
                          e.stopPropagation()
                          if (!isPendingEvent(event)) {
                            onEventClick(event, anchorFromEvent(e))
                          }
                        }}
                      />
                    </div>
                  )
                })}
                {hidden > 0 && (
                  <button
                    type="button"
                    className="w-full truncate rounded-md px-1.5 py-[1px] text-left text-xs font-medium text-muted-foreground hover:bg-mail-nav-hover"
                    onPointerDown={(e) => e.stopPropagation()}
                    onClick={(e) => {
                      e.stopPropagation()
                      onOpenDay(day)
                    }}
                  >
                    {hidden} autre{hidden > 1 ? "s" : ""}
                  </button>
                )}
              </div>
            )
          }),
        )}
      </div>
    </div>
  )
}
