"use client"

import {
  useEffect,
  useMemo,
  useRef,
  useState,
  type MouseEvent,
  type PointerEvent as ReactPointerEvent,
} from "react"
import { addDays, format, isSameDay, 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 { formatEventTime, formatHourLabel, roundToStep } from "@/lib/agenda/agenda-date"
import { readableTextColor } from "@/lib/agenda/agenda-colors"
import { layoutDayEvents } from "@/lib/agenda/agenda-event-layout"
import { bindAgendaEventDragSession } from "@/lib/agenda/agenda-event-drag-session"
import { eventsOnDay, isMultiDay } from "@/lib/agenda/agenda-events"
import { isPendingEvent } from "@/lib/agenda/agenda-pending-event"
import { useEffectiveAgendaSettings } from "@/lib/agenda/use-effective-agenda-settings"
import type { AgendaEvent } from "@/lib/agenda/agenda-types"
import { cn } from "@/lib/utils"

const HOUR_PX = 48
const GUTTER_PX = 56
const MIN_EVENT_PX = 22

/** Scroll sans barre visible — évite le décalage header / colonnes. */
const SCROLL_CLASS =
  "min-h-0 flex-1 overflow-y-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden"

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

interface DragState {
  startDayIndex: number
  endDayIndex: number
  anchorMin: number
  startMin: number
  endMin: number
  moved: boolean
  /** Glissé horizontalement sur plusieurs colonnes → journée entière. */
  multiDay: boolean
}

interface EventMoveState {
  event: AgendaEvent
  durationMin: number
  originDayIndex: number
  originStartMin: number
  currentDayIndex: number
  currentStartMin: number
  positionChanged: boolean
  /** Offset pointeur vs haut événement — évite snap au clic/début drag. */
  grabOffsetY: number
}

export function AgendaViewWeek({
  days,
  events,
  pendingEvent,
  onCreateRange,
  onEventClick,
  onEventMove,
  onOpenDay,
}: {
  days: Date[]
  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 scrollRef = useRef<HTMLDivElement>(null)
  const columnRefs = useRef<(HTMLDivElement | null)[]>([])
  const dragRef = useRef<DragState | null>(null)
  const eventMoveRef = useRef<EventMoveState | null>(null)
  const dragSessionCleanupRef = useRef<(() => void) | null>(null)
  const suppressClickRef = useRef(false)
  const [drag, setDrag] = useState<DragState | null>(null)
  const [eventMove, setEventMove] = useState<EventMoveState | null>(null)
  const [now, setNow] = useState(() => new Date())
  const settings = useEffectiveAgendaSettings()
  const {
    visibleHoursStart,
    visibleHoursEnd,
    dragSnapMinutes,
    defaultQuickDurationMinutes,
    timeFormat,
  } = settings
  const gridMinutes = Math.max(60, visibleHoursEnd - visibleHoursStart + 1)
  const gridHeightPx = (gridMinutes / 60) * HOUR_PX
  const hourMarks = useMemo(() => {
    const startHour = Math.floor(visibleHoursStart / 60)
    const endHour = Math.ceil(visibleHoursEnd / 60)
    const marks: number[] = []
    for (let h = startHour + 1; h <= endHour; h++) marks.push(h)
    return marks
  }, [visibleHoursStart, visibleHoursEnd])

  useEffect(() => {
    const id = window.setInterval(() => setNow(new Date()), 60_000)
    return () => window.clearInterval(id)
  }, [])

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

  useEffect(() => {
    const el = scrollRef.current
    if (!el) return
    const nowMinutes = now.getHours() * 60 + now.getMinutes()
    const target = Math.max(
      0,
      (Math.min(nowMinutes, visibleHoursEnd) - visibleHoursStart - 90) * (HOUR_PX / 60),
    )
    el.scrollTop = target
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [visibleHoursStart, visibleHoursEnd])

  const dayIndexFromClientX = (clientX: number): number | null => {
    for (let i = 0; i < columnRefs.current.length; i++) {
      const el = columnRefs.current[i]
      if (!el) continue
      const rect = el.getBoundingClientRect()
      if (clientX >= rect.left && clientX <= rect.right) return i
    }
    return null
  }

  const perDay = useMemo(() => {
    const merged = pendingEvent ? [...events, pendingEvent] : events
    return days.map((day) => {
      const dayEvents = eventsOnDay(merged, day)
      const banners = dayEvents
        .filter((e) => e.allDay || isMultiDay(e))
        .sort((a, b) => {
          if (isPendingEvent(a)) return 1
          if (isPendingEvent(b)) return -1
          return a.start.getTime() - b.start.getTime()
        })
      const timed = dayEvents.filter((e) => !e.allDay && !isMultiDay(e))
      return { day, banners, positioned: layoutDayEvents(timed, day) }
    })
  }, [days, events, pendingEvent])

  const hasBanners =
    perDay.some((d) => d.banners.length > 0) || (drag?.multiDay ?? false)
  const colTemplate = `${GUTTER_PX}px repeat(${days.length}, minmax(0, 1fr))`

  const minuteFromClientY = (clientY: number, dayIndex: number): number | null => {
    const el = columnRefs.current[dayIndex]
    if (!el) return null
    const rect = el.getBoundingClientRect()
    const relativeMinutes = ((clientY - rect.top) / HOUR_PX) * 60
    const absolute = visibleHoursStart + relativeMinutes
    return Math.min(
      visibleHoursEnd,
      Math.max(visibleHoursStart, roundToStep(absolute, dragSnapMinutes)),
    )
  }

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

    const minDay = Math.min(d.startDayIndex, d.endDayIndex)
    const maxDay = Math.max(d.startDayIndex, d.endDayIndex)
    const viaDrag = d.moved

    if (d.multiDay) {
      const start = startOfDay(days[minDay])
      const end = addDays(startOfDay(days[maxDay]), 1)
      const el = columnRefs.current[minDay]
      const anchor: AnchorRect = el
        ? {
            left: el.getBoundingClientRect().left,
            top: el.getBoundingClientRect().top,
            width:
              (columnRefs.current[maxDay]?.getBoundingClientRect().right ?? el.getBoundingClientRect().right) -
              el.getBoundingClientRect().left,
            height: 24,
          }
        : { left: 0, top: 0, width: 0, height: 0 }
      onCreateRange(start, end, true, anchor, true)
      return
    }

    const day = days[d.startDayIndex]
    const el = columnRefs.current[d.startDayIndex]
    if (!el) return

    const startMin = d.startMin
    const endMin = viaDrag
      ? d.endMin
      : Math.min(visibleHoursEnd, d.startMin + defaultQuickDurationMinutes)
    const start = new Date(day)
    start.setHours(0, startMin, 0, 0)
    const end = new Date(day)
    end.setHours(0, Math.max(endMin, startMin + dragSnapMinutes), 0, 0)

    const colRect = el.getBoundingClientRect()
    onCreateRange(start, end, false, {
      left: colRect.left,
      top: colRect.top + ((startMin - visibleHoursStart) / 60) * HOUR_PX,
      width: colRect.width,
      height: Math.max(((endMin - startMin) / 60) * HOUR_PX, MIN_EVENT_PX),
    }, viaDrag)
  }

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

    const positionChanged = move.event.allDay
      ? move.currentDayIndex !== move.originDayIndex
      : move.currentDayIndex !== move.originDayIndex ||
        move.currentStartMin !== move.originStartMin

    if (didDrag) {
      suppressClickRef.current = true
    }
    if (!didDrag || !positionChanged) return

    const day = days[move.currentDayIndex]
    if (!day) return

    if (move.event.allDay) {
      onEventMove(move.event, startOfDay(day))
      return
    }

    const start = new Date(day)
    start.setHours(0, move.currentStartMin, 0, 0)
    onEventMove(move.event, start)
  }

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

    const hoverDay = dayIndexFromClientX(clientX)
    if (hoverDay === null) return

    let currentStartMin = move.originStartMin
    if (!move.event.allDay) {
      const min = minuteFromClientY(clientY - move.grabOffsetY, hoverDay)
      if (min === null) return
      currentStartMin = min
    }

    const positionChanged = move.event.allDay
      ? hoverDay !== move.originDayIndex
      : hoverDay !== move.originDayIndex || currentStartMin !== move.originStartMin

    const next: EventMoveState = {
      ...move,
      currentDayIndex: hoverDay,
      currentStartMin,
      positionChanged,
    }
    eventMoveRef.current = next
    setEventMove(next)
  }

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

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

      const originDayIndex = days.findIndex((d) => isSameDay(d, event.start))
      const resolvedOriginDay = originDayIndex >= 0 ? originDayIndex : displayDayIndex
      const originStartMin = event.allDay
        ? 0
        : event.start.getHours() * 60 + event.start.getMinutes()
      const durationMin = Math.max(
        dragSnapMinutes,
        Math.round((event.end.getTime() - event.start.getTime()) / 60_000),
      )
      const grabOffsetY = e.clientY - e.currentTarget.getBoundingClientRect().top
      const next: EventMoveState = {
        event,
        durationMin,
        originDayIndex: resolvedOriginDay,
        originStartMin,
        currentDayIndex: resolvedOriginDay,
        currentStartMin: originStartMin,
        positionChanged: false,
        grabOffsetY,
      }
      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 handlePointerDown = (dayIndex: number) => (e: ReactPointerEvent<HTMLDivElement>) => {
    if (e.button !== 0 || eventMoveRef.current) return
    if ((e.target as HTMLElement).closest("[data-agenda-event]")) return
    const min = minuteFromClientY(e.clientY, dayIndex)
    if (min === null) return
    const next: DragState = {
      startDayIndex: dayIndex,
      endDayIndex: dayIndex,
      anchorMin: min,
      startMin: min,
      endMin: min + dragSnapMinutes,
      moved: false,
      multiDay: false,
    }
    dragRef.current = next
    setDrag(next)
    e.currentTarget.setPointerCapture(e.pointerId)
  }

  const handlePointerMove = (e: ReactPointerEvent<HTMLDivElement>) => {
    const d = dragRef.current
    if (!d) return

    const hoverDay = dayIndexFromClientX(e.clientX)
    if (hoverDay === null) return

    if (hoverDay !== d.startDayIndex) {
      const next: DragState = {
        ...d,
        endDayIndex: hoverDay,
        moved: true,
        multiDay: true,
      }
      dragRef.current = next
      setDrag(next)
      return
    }

    if (d.multiDay) return

    const min = minuteFromClientY(e.clientY, d.startDayIndex)
    if (min === null) return
    if (min === d.anchorMin && !d.moved) return
    const next: DragState = {
      ...d,
      endDayIndex: d.startDayIndex,
      moved: true,
      multiDay: false,
      startMin: Math.min(d.anchorMin, min),
      endMin: Math.max(
        d.anchorMin,
        min,
        Math.min(d.anchorMin, min) + dragSnapMinutes,
      ),
    }
    dragRef.current = next
    setDrag(next)
  }

  const handlePointerUp = () => {
    finishDrag()
  }

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

  const dragMultiDayRange =
    drag?.multiDay && drag.moved
      ? {
          min: Math.min(drag.startDayIndex, drag.endDayIndex),
          max: Math.max(drag.startDayIndex, drag.endDayIndex),
        }
      : null

  return (
    <div className="flex h-full min-h-0 flex-col overflow-hidden bg-mail-surface">
      {/* En-tête : jours + rangée journée entière */}
      <div className="shrink-0 border-b border-border/60">
        <div className="grid" style={{ gridTemplateColumns: colTemplate }}>
          <div />
          {days.map((day) => {
            const isToday = isSameDay(day, now)
            return (
              <div
                key={day.getTime()}
                className="flex flex-col items-center gap-0.5 border-l border-border/40 pt-2 pb-1"
              >
                <span className="text-[0.7rem] font-medium tracking-wide text-muted-foreground uppercase">
                  {format(day, "EEE", { locale: fr }).replace(".", "")}
                </span>
                <button
                  type="button"
                  onClick={() => onOpenDay(day)}
                  className={cn(
                    "flex size-10 items-center justify-center rounded-full text-[1.45rem] font-normal text-foreground/80 hover:bg-mail-nav-hover",
                    isToday &&
                      "bg-primary font-medium text-primary-foreground hover:bg-primary",
                  )}
                >
                  {day.getDate()}
                </button>
              </div>
            )
          })}
        </div>
        {hasBanners && (
          <div className="grid" style={{ gridTemplateColumns: colTemplate }}>
            <div className="pt-0.5 pr-2 text-right text-[0.65rem] text-muted-foreground" />
            {perDay.map(({ day, banners }, dayIndex) => {
              const inDragRange =
                dragMultiDayRange &&
                dayIndex >= dragMultiDayRange.min &&
                dayIndex <= dragMultiDayRange.max
              const inEventMoveTarget =
                eventMove?.positionChanged &&
                eventMove.event.allDay &&
                eventMove.currentDayIndex === dayIndex
              return (
                <div
                  key={day.getTime()}
                  className="relative flex min-h-6 flex-col gap-0.5 border-l border-border/40 px-0.5 pb-1"
                >
                  {inDragRange && (
                    <div
                      aria-hidden
                      className={cn(
                        "absolute inset-x-0.5 top-0.5 bottom-0.5 rounded-md bg-primary/25 ring-1 ring-primary/50",
                        dayIndex === dragMultiDayRange!.min && "rounded-r-none",
                        dayIndex === dragMultiDayRange!.max && "rounded-l-none",
                        dragMultiDayRange!.min !== dragMultiDayRange!.max &&
                          dayIndex > dragMultiDayRange!.min &&
                          dayIndex < dragMultiDayRange!.max &&
                          "rounded-none",
                      )}
                    />
                  )}
                  {inEventMoveTarget && (
                    <div
                      aria-hidden
                      className="absolute inset-x-0.5 top-0.5 bottom-0.5 rounded-md bg-primary/25 ring-1 ring-primary/50"
                    />
                  )}
                  {banners.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, dayIndex)}
                    >
                      <AgendaEventChip
                        event={event}
                        filled
                        pending={isPendingEvent(event)}
                        onClick={(e) => {
                          if (shouldSuppressEventClick()) {
                            e.preventDefault()
                            return
                          }
                          e.stopPropagation()
                          if (!isPendingEvent(event)) {
                            onEventClick(event, anchorFromEvent(e))
                          }
                        }}
                      />
                    </div>
                    )
                  })}
                </div>
              )
            })}
          </div>
        )}
      </div>

      {/* Grille horaire */}
      <div ref={scrollRef} className={SCROLL_CLASS}>
        <div
          className="relative grid"
          style={{ gridTemplateColumns: colTemplate, height: gridHeightPx }}
        >
          <div className="relative">
            {hourMarks.map((h) => (
              <span
                key={h}
                className="absolute right-2 -translate-y-1/2 text-[0.65rem] text-muted-foreground"
                style={{ top: ((h * 60 - visibleHoursStart) / 60) * HOUR_PX }}
              >
                {formatHourLabel(h % 24, timeFormat)}
              </span>
            ))}
          </div>

          {perDay.map(({ day, positioned }, dayIndex) => {
            const isToday = isSameDay(day, now)
            const nowMinutes = now.getHours() * 60 + now.getMinutes()
            const nowTop = (nowMinutes - visibleHoursStart) * (HOUR_PX / 60)
            const showNowLine =
              nowMinutes >= visibleHoursStart && nowMinutes <= visibleHoursEnd
            const showTimedDrag =
              drag && !drag.multiDay && drag.moved && drag.startDayIndex === dayIndex
            const showEventMove =
              eventMove?.positionChanged &&
              eventMove.currentDayIndex === dayIndex &&
              !eventMove.event.allDay

            return (
              <div
                key={day.getTime()}
                ref={(el) => {
                  columnRefs.current[dayIndex] = el
                }}
                className="relative cursor-pointer touch-none border-l border-border/40"
                onPointerDown={handlePointerDown(dayIndex)}
                onPointerMove={handlePointerMove}
                onPointerUp={handlePointerUp}
                onPointerCancel={handlePointerCancel}
              >
                {hourMarks.map((h) => (
                  <div
                    key={h}
                    aria-hidden
                    className="absolute right-0 left-0 border-t border-border/40"
                    style={{ top: ((h * 60 - visibleHoursStart) / 60) * HOUR_PX }}
                  />
                ))}

                {showTimedDrag && (
                  <div
                    aria-hidden
                    className="absolute right-1 left-0.5 z-10 rounded-md bg-primary/25 ring-1 ring-primary/50"
                    style={{
                      top: ((drag.startMin - visibleHoursStart) / 60) * HOUR_PX,
                      height: Math.max(
                        ((drag.endMin - drag.startMin) / 60) * HOUR_PX,
                        8,
                      ),
                    }}
                  >
                    <span className="px-1.5 text-[0.65rem] font-medium text-primary">
                      {formatMinutes(drag.startMin, timeFormat)} –{" "}
                      {formatMinutes(drag.endMin, timeFormat)}
                    </span>
                  </div>
                )}

                {showEventMove && eventMove && (
                  <div
                    aria-hidden
                    className="absolute right-1 left-0.5 z-10 rounded-md bg-primary/25 ring-1 ring-primary/50"
                    style={{
                      top: ((eventMove.currentStartMin - visibleHoursStart) / 60) * HOUR_PX,
                      height: Math.max(
                        (eventMove.durationMin / 60) * HOUR_PX,
                        MIN_EVENT_PX,
                      ),
                    }}
                  >
                    <span className="px-1.5 text-[0.65rem] font-medium text-primary">
                      {formatMinutes(eventMove.currentStartMin, timeFormat)} –{" "}
                      {formatMinutes(
                        eventMove.currentStartMin + eventMove.durationMin,
                        timeFormat,
                      )}
                    </span>
                  </div>
                )}

                {positioned.map(({ event, top, duration, leftPct, widthPct }) => {
                  const isDragging =
                    eventMove?.event.key === event.key && eventMove.positionChanged
                  const compact = (duration / 60) * HOUR_PX < 40
                  const pending = isPendingEvent(event)
                  return (
                    <div
                      key={event.key}
                      role={pending ? "presentation" : "button"}
                      tabIndex={pending ? undefined : 0}
                      data-agenda-event
                      className={cn(
                        "absolute z-20 flex flex-col overflow-hidden rounded-md px-1.5 py-0.5 text-left shadow-sm ring-1 ring-black/5 transition-[filter,opacity] hover:z-30 hover:brightness-95 dark:ring-white/10 dark:hover:brightness-110",
                        pending && "pointer-events-none z-[25] ring-2 ring-dashed ring-primary/60",
                        !pending && onEventMove && "cursor-grab touch-none active:cursor-grabbing",
                        !pending && !onEventMove && "cursor-pointer",
                        isDragging && "opacity-40",
                      )}
                      style={{
                        top: ((top - visibleHoursStart) / 60) * HOUR_PX,
                        height: Math.max((duration / 60) * HOUR_PX - 2, MIN_EVENT_PX),
                        left: `calc(${leftPct}% + 1px)`,
                        width: `calc(${widthPct}% - 3px)`,
                        backgroundColor: pending ? `${event.color}99` : event.color,
                        color: readableTextColor(event.color),
                      }}
                      onClick={
                        pending
                          ? undefined
                          : (e) => {
                              if (shouldSuppressEventClick()) {
                                e.preventDefault()
                                return
                              }
                              e.stopPropagation()
                              onEventClick(event, anchorFromEvent(e as unknown as MouseEvent<HTMLElement>))
                            }
                      }
                      onPointerDown={
                        pending ? undefined : startEventMove(event, dayIndex)
                      }
                      onKeyDown={
                        pending
                          ? undefined
                          : (e) => {
                              if (e.key === "Enter" || e.key === " ") {
                                e.stopPropagation()
                                onEventClick(event, {
                                  left: 0,
                                  top: 0,
                                  width: 0,
                                  height: 0,
                                })
                              }
                            }
                      }
                    >
                      <span
                        className={cn(
                          "truncate text-xs leading-tight font-medium",
                          compact && "text-[0.7rem]",
                        )}
                      >
                        {event.title}
                        {compact && (
                          <span className="font-normal opacity-90">
                            {" "}
                            ⋅ {formatEventTime(event.start, timeFormat)}
                          </span>
                        )}
                      </span>
                      {!compact && (
                        <span className="truncate text-[0.7rem] leading-tight opacity-90">
                          {formatEventTime(event.start, timeFormat)} –{" "}
                          {formatEventTime(event.end, timeFormat)}
                        </span>
                      )}
                    </div>
                  )
                })}

                {isToday && showNowLine && (
                  <div
                    aria-hidden
                    className="pointer-events-none absolute right-0 left-0 z-30"
                    style={{ top: nowTop }}
                  >
                    <div className="relative h-0.5 bg-red-500">
                      <span className="absolute top-1/2 -left-1 size-3 -translate-y-1/2 rounded-full bg-red-500" />
                    </div>
                  </div>
                )}
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}

function formatMinutes(min: number, timeFormat: "24h" | "12h" = "24h"): string {
  const d = new Date(2000, 0, 1, Math.floor(min / 60), min % 60, 0)
  return formatEventTime(d, timeFormat)
}
