import * as React from 'react'
import { cn } from './utils'
import {
  buildDayGrid,
  DAYS_MON,
  DAYS_SUN,
  isBetween,
  isDateDisabled as sharedIsDateDisabled,
  isSameDay,
  MONTHS,
  stripTime,
  type WeekStart,
} from '../../utils/date'

export type { WeekStart } from '../../utils/date'

export interface CalendarProps {
  /** Selected date (single mode) */
  value?: Date | null | undefined
  /** Range start date (range mode) */
  rangeStart?: Date | null | undefined
  /** Range end date (range mode) */
  rangeEnd?: Date | null | undefined
  /** Selection mode */
  mode?: 'single' | 'range' | undefined
  /** Minimum selectable date */
  minDate?: Date | undefined
  /** Maximum selectable date */
  maxDate?: Date | undefined
  /** Dates that cannot be selected */
  disabledDates?: Date[] | ((date: Date) => boolean) | undefined
  /** First day of week: 0 = Sunday, 1 = Monday */
  weekStart?: WeekStart | undefined
  /** Whether to render days outside the current month */
  showOutsideDays?: boolean | undefined
  /** Additional class name */
  className?: string
  /** Callback when a single date is selected */
  onChange?: (date: Date) => void
  /** Callback when a range is completed */
  onRangeChange?: (start: Date | null, end: Date | null) => void
}

function Calendar({
  value,
  rangeStart: rangeStartProp,
  rangeEnd: rangeEndProp,
  mode = 'single',
  minDate,
  maxDate,
  disabledDates,
  weekStart = 0,
  showOutsideDays = true,
  className,
  onChange,
  onRangeChange,
}: CalendarProps) {
  const today = React.useMemo(() => stripTime(new Date()), [])
  const initial = value ?? rangeStartProp ?? today

  const [viewYear, setViewYear] = React.useState(initial.getFullYear())
  const [viewMonth, setViewMonth] = React.useState(initial.getMonth())
  const [selected, setSelected] = React.useState<Date | null>(value ? stripTime(value) : null)
  const [internalRangeStart, setInternalRangeStart] = React.useState<Date | null>(
    rangeStartProp ? stripTime(rangeStartProp) : null
  )
  const [internalRangeEnd, setInternalRangeEnd] = React.useState<Date | null>(
    rangeEndProp ? stripTime(rangeEndProp) : null
  )
  const [hoverDate, setHoverDate] = React.useState<Date | null>(null)

  // Sync external props
  React.useEffect(() => {
    if (value !== undefined) {
      setSelected(value ? stripTime(value) : null)
      if (value) {
        setViewYear(value.getFullYear())
        setViewMonth(value.getMonth())
      }
    }
  }, [value])

  React.useEffect(() => {
    if (rangeStartProp !== undefined)
      setInternalRangeStart(rangeStartProp ? stripTime(rangeStartProp) : null)
  }, [rangeStartProp])

  React.useEffect(() => {
    if (rangeEndProp !== undefined)
      setInternalRangeEnd(rangeEndProp ? stripTime(rangeEndProp) : null)
  }, [rangeEndProp])

  const dayLabels = weekStart === 1 ? DAYS_MON : DAYS_SUN
  const days = React.useMemo(
    () => buildDayGrid(viewYear, viewMonth, weekStart),
    [viewYear, viewMonth, weekStart]
  )

  const isDateDisabled = React.useCallback(
    (date: Date): boolean => sharedIsDateDisabled(date, minDate, maxDate, disabledDates),
    [minDate, maxDate, disabledDates]
  )

  function navigateMonth(delta: number) {
    setViewMonth(prev => {
      let m = prev + delta
      if (m > 11) {
        m = 0
        setViewYear(y => y + 1)
      } else if (m < 0) {
        m = 11
        setViewYear(y => y - 1)
      }
      return m
    })
  }

  function selectDate(date: Date) {
    const d = stripTime(date)
    if (mode === 'range') {
      if (internalRangeStart === null || internalRangeEnd !== null) {
        setInternalRangeStart(d)
        setInternalRangeEnd(null)
      } else {
        let start = internalRangeStart
        let end = d
        if (d < start) {
          end = start
          start = d
        }
        setInternalRangeStart(start)
        setInternalRangeEnd(end)
        onRangeChange?.(start, end)
      }
    } else {
      setSelected(d)
      onChange?.(d)
    }
  }

  function getDayClasses(date: Date): string {
    const isOutside = date.getMonth() !== viewMonth
    const disabled = isDateDisabled(date)
    const isToday = isSameDay(date, today)
    const rangeEnd = internalRangeEnd ?? hoverDate

    let isSelected = false
    let isRangeStart = false
    let isRangeEnd = false
    let isInRange = false

    if (mode === 'range') {
      if (internalRangeStart && isSameDay(date, internalRangeStart)) {
        isRangeStart = true
        isSelected = true
      }
      if (rangeEnd && isSameDay(date, rangeEnd)) {
        isRangeEnd = true
        isSelected = true
      }
      if (internalRangeStart && rangeEnd && isBetween(date, internalRangeStart, rangeEnd)) {
        isInRange = true
      }
    } else if (selected && isSameDay(date, selected)) {
      isSelected = true
    }

    return cn(
      'inline-flex h-9 w-9 items-center justify-center rounded-lg text-[13px] transition-all duration-200',
      'hover:bg-glass-hover focus:outline-none focus:border-primary focus:shadow-glow',
      isOutside && 'text-foreground-muted',
      isOutside && !showOutsideDays && 'invisible',
      disabled && 'pointer-events-none opacity-30',
      isToday && !isSelected && 'border border-primary/50 font-semibold text-primary',
      isSelected && 'bg-primary text-white font-semibold shadow-glow',
      isRangeStart && 'rounded-r-none',
      isRangeEnd && 'rounded-l-none',
      isInRange && 'bg-primary/15 text-foreground rounded-none'
    )
  }

  const weeks = React.useMemo(() => {
    const result: Date[][] = []
    for (let i = 0; i < days.length; i += 7) {
      result.push(days.slice(i, i + 7))
    }
    return result
  }, [days])

  return (
    <div data-slot="calendar" role="group" className={cn('w-fit', className)}>
      {/* Header */}
      <div className="flex items-center justify-between px-1 pb-3">
        <button
          type="button"
          aria-label="Previous month"
          onClick={() => navigateMonth(-1)}
          className={cn(
            'inline-flex h-8 w-8 items-center justify-center rounded-lg transition-all duration-200',
            'text-foreground-secondary hover:bg-glass-hover hover:text-foreground'
          )}
        >
          <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
            <path
              d="M10 12L6 8L10 4"
              stroke="currentColor"
              strokeWidth="1.5"
              strokeLinecap="round"
              strokeLinejoin="round"
            />
          </svg>
        </button>
        <span className="text-[13px] font-semibold text-foreground">
          {MONTHS[viewMonth]} {viewYear}
        </span>
        <button
          type="button"
          aria-label="Next month"
          onClick={() => navigateMonth(1)}
          className={cn(
            'inline-flex h-8 w-8 items-center justify-center rounded-lg transition-all duration-200',
            'text-foreground-secondary hover:bg-glass-hover hover:text-foreground'
          )}
        >
          <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
            <path
              d="M6 4L10 8L6 12"
              stroke="currentColor"
              strokeWidth="1.5"
              strokeLinecap="round"
              strokeLinejoin="round"
            />
          </svg>
        </button>
      </div>

      {/* Day grid */}
      <div role="grid" className="select-none">
        {/* Weekday headers */}
        <div role="row" className="mb-1 grid grid-cols-7 gap-0">
          {dayLabels.map(label => (
            <span
              key={label}
              role="columnheader"
              aria-label={label}
              className="flex h-9 w-9 items-center justify-center text-[11px] font-medium text-foreground-muted"
            >
              {label}
            </span>
          ))}
        </div>

        {/* Weeks */}
        {weeks.map((week, wi) => (
          <div key={wi} role="row" className="grid grid-cols-7 gap-0">
            {week.map((day, di) => {
              const disabled = isDateDisabled(day)
              return (
                <button
                  key={di}
                  type="button"
                  role="gridcell"
                  aria-disabled={disabled || undefined}
                  aria-selected={
                    (mode !== 'range' && selected && isSameDay(day, selected)) ||
                    (mode === 'range' &&
                      internalRangeStart &&
                      isSameDay(day, internalRangeStart)) ||
                    (mode === 'range' && internalRangeEnd && isSameDay(day, internalRangeEnd)) ||
                    undefined
                  }
                  tabIndex={disabled ? -1 : 0}
                  className={getDayClasses(day)}
                  onClick={() => !disabled && selectDate(day)}
                  onMouseEnter={() => mode === 'range' && setHoverDate(day)}
                  onMouseLeave={() => mode === 'range' && setHoverDate(null)}
                >
                  {day.getDate()}
                </button>
              )
            })}
          </div>
        ))}
      </div>
    </div>
  )
}

export { Calendar }
