import React, {
  useCallback,
  useEffect,
  useRef,
  useState,
} from 'react'
import { createPortal } from 'react-dom'

const MONTH_NAMES = [
  'January', 'February', 'March', 'April', 'May', 'June',
  'July', 'August', 'September', 'October', 'November', 'December',
]

const YEAR_MIN = 1900
const YEAR_MAX = 2105

const DAY_ABBR = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']

function pad(value) {
  return String(value).padStart(2, '0')
}

function daysInMonth(year, month) {
  return new Date(year, month + 1, 0).getDate()
}

function firstWeekday(year, month) {
  const day = new Date(year, month, 1).getDay()
  return (day + 6) % 7
}

function toISODate(date) {
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
}

function parseDatePart(value) {
  if (!value) return null

  const [year, month, day] = value.split('-').map(Number)

  if (!year || !month || !day) return null

  return new Date(year, month - 1, day)
}

function normalizeDateTimeInput(value) {
  const raw = String(value || '').trim()
  if (!raw) return { date: '', time: '' }

  const match = raw.match(/^(\d{4}-\d{2}-\d{2})(?:[ T](\d{2}:\d{2})(?::\d{2})?)?(?:Z|[+-]\d{2}:?\d{2})?$/)
  if (match) {
    return {
      date: match[1],
      time: match[2] || '',
    }
  }

  const parsed = new Date(raw)
  if (Number.isNaN(parsed.getTime())) {
    return { date: raw, time: '' }
  }

  return {
    date: toISODate(parsed),
    time: `${pad(parsed.getHours())}:${pad(parsed.getMinutes())}`,
  }
}

function splitDateTime(value) {
  const normalized = normalizeDateTimeInput(value)

  return {
    date: normalized.date,
    time: normalized.time.slice(0, 5),
  }
}

function mergeDateTime(date, time) {
  if (!date) return ''
  return `${date}T${time || '00:00'}`
}

function maxDateValue(a, b) {
  if (!a) return b || ''
  if (!b) return a || ''
  return a > b ? a : b
}

function minDateValue(a, b) {
  if (!a) return b || ''
  if (!b) return a || ''
  return a < b ? a : b
}

function clampTimeToBounds(date, time, minDateTime, maxDateTime) {
  const nextTime = time || '00:00'
  const minParts = splitDateTime(minDateTime)
  const maxParts = splitDateTime(maxDateTime)

  if (date && minParts.date === date && minParts.time && nextTime < minParts.time) {
    return minParts.time
  }

  if (date && maxParts.date === date && maxParts.time && nextTime > maxParts.time) {
    return maxParts.time
  }

  return nextTime
}

function formatDisplay(value) {
  if (!value) return ''

  const { date, time } = splitDateTime(value)
  const parsed = parseDatePart(date)

  if (!parsed) return ''

  return `${MONTH_NAMES[parsed.getMonth()].slice(0, 3)} ${parsed.getDate()}, ${parsed.getFullYear()}${time ? ` at ${time}` : ''}`
}

function isSameDay(a, b) {
  return a?.getFullYear() === b?.getFullYear()
    && a?.getMonth() === b?.getMonth()
    && a?.getDate() === b?.getDate()
}

function CalendarGrid({ year, month, selectedDate, onSelect, minDate, maxDate }) {
  const count = daysInMonth(year, month)
  const start = firstWeekday(year, month)
  const prevMonth = month - 1 < 0 ? 11 : month - 1
  const prevYear = month - 1 < 0 ? year - 1 : year
  const prevCount = daysInMonth(prevYear, prevMonth)
  const cells = []

  for (let index = start - 1; index >= 0; index -= 1) {
    cells.push({
      day: prevCount - index,
      current: false,
      date: new Date(prevYear, prevMonth, prevCount - index),
    })
  }

  for (let day = 1; day <= count; day += 1) {
    cells.push({ day, current: true, date: new Date(year, month, day) })
  }

  let nextDay = 1
  while (cells.length % 7 !== 0) {
    cells.push({ day: nextDay, current: false, date: new Date(year, month + 1, nextDay) })
    nextDay += 1
  }

  const today = new Date()
  today.setHours(0, 0, 0, 0)

  return (
    <div className="p-3">
      <div className="mb-1 grid grid-cols-7">
        {DAY_ABBR.map((day) => (
          <div key={day} className="py-1 text-center text-[10px] font-semibold text-slate-500">{day}</div>
        ))}
      </div>

      <div className="grid grid-cols-7 gap-y-0.5">
        {cells.map((cell, index) => {
          const iso = toISODate(cell.date)
          const selected = isSameDay(cell.date, selectedDate)
          const todayCell = isSameDay(cell.date, today)
          const disabled = (minDate && iso < minDate) || (maxDate && iso > maxDate)

          return (
            <button
              key={`${iso}-${index}`}
              type="button"
              disabled={disabled}
              onClick={() => onSelect(iso)}
              className={[
                'relative mx-auto flex h-8 w-8 items-center justify-center rounded-lg text-sm transition-all',
                !cell.current ? 'text-slate-600' : '',
                cell.current && !selected && !disabled ? 'text-white hover:bg-white/10' : '',
                selected ? 'bg-accent font-semibold text-white shadow shadow-accent/30' : '',
                todayCell && !selected ? 'text-accent ring-1 ring-accent/50' : '',
                disabled ? 'cursor-not-allowed opacity-30' : 'cursor-pointer',
              ].join(' ')}
            >
              {cell.day}
            </button>
          )
        })}
      </div>
    </div>
  )
}

export default function DateTimePicker({
  value = '',
  onChange,
  label,
  placeholder,
  error,
  hint,
  required = false,
  clearable = false,
  id,
  disabled = false,
  mode = 'datetime',
  minDate,
  maxDate,
  minDateTime,
  maxDateTime,
  className = '',
}) {
  const today = new Date()
  const initial = splitDateTime(value)
  const initialDate = parseDatePart(initial.date) || today

  const [open, setOpen] = useState(false)
  const [dropPos, setDropPos] = useState({ top: 0, left: 0, width: 320 })
  const [viewYear, setViewYear] = useState(initialDate.getFullYear())
  const [viewMonth, setViewMonth] = useState(initialDate.getMonth())
  const [draftDate, setDraftDate] = useState(initial.date)
  // default to current local time (HH:MM), clamped to min/max bounds when present
  const nowTime = `${pad(today.getHours())}:${pad(today.getMinutes())}`
  const defaultDraftTime = (function () {
    const baseDate = initial.date || toISODate(initialDate)
    const candidate = initial.time || nowTime
    return clampTimeToBounds(baseDate, candidate, minDateTime, maxDateTime)
  })()
  const [draftTime, setDraftTime] = useState(defaultDraftTime)
  const effectivePlaceholder = placeholder || (mode === 'date' ? 'Pick a date' : 'Pick a date and time')

  const triggerRef = useRef(null)
  const inputId = id ?? (label ? `dtp-${label.toLowerCase().replace(/\s+/g, '-')}` : 'date-time-picker')
  const panelId = `dtp-panel-${inputId}`

  useEffect(() => {
    const next = splitDateTime(value)
    setDraftDate(next.date)

    // prefer explicit time, otherwise use current time clamped to bounds for the chosen date
    const fallbackTime = (() => {
      const candidate = next.time || `${pad(new Date().getHours())}:${pad(new Date().getMinutes())}`
      const dateForClamp = next.date || toISODate(initialDate)
      return clampTimeToBounds(dateForClamp, candidate, minDateTime, maxDateTime)
    })()

    setDraftTime(next.time || fallbackTime)

    const nextDate = parseDatePart(next.date)
    if (nextDate) {
      setViewYear(nextDate.getFullYear())
      setViewMonth(nextDate.getMonth())
    }
  }, [value])

  const measure = useCallback(() => {
    if (!triggerRef.current) return

    const rect = triggerRef.current.getBoundingClientRect()
    const panelWidth = Math.max(rect.width, 320)
    const panelHeight = 420
    const openUp = window.innerHeight - rect.bottom < panelHeight + 8 && rect.top > panelHeight + 8

    setDropPos({
      top: openUp ? rect.top - panelHeight - 4 : rect.bottom + 4,
      left: Math.min(rect.left, window.innerWidth - panelWidth - 8),
      width: panelWidth,
    })
  }, [])

  const openPicker = useCallback(() => {
    if (disabled) return
    measure()
    setOpen(true)
  }, [disabled, measure])

  useEffect(() => {
    if (!open) return undefined

    const handleMouseDown = (event) => {
      if (!triggerRef.current?.contains(event.target) && !document.getElementById(panelId)?.contains(event.target)) {
        setOpen(false)
      }
    }

    document.addEventListener('mousedown', handleMouseDown)
    return () => document.removeEventListener('mousedown', handleMouseDown)
  }, [open, panelId])

  useEffect(() => {
    if (!open) return undefined

    const handleScroll = (event) => {
      if (document.getElementById(panelId)?.contains(event.target)) return
      setOpen(false)
    }

    const handleResize = () => setOpen(false)

    window.addEventListener('scroll', handleScroll, true)
    window.addEventListener('resize', handleResize)

    return () => {
      window.removeEventListener('scroll', handleScroll, true)
      window.removeEventListener('resize', handleResize)
    }
  }, [open, panelId])

  const applyValue = useCallback((date, time) => {
    if (!date) {
      onChange?.('')
      return
    }

    onChange?.(mode === 'date' ? date : mergeDateTime(date, time))
  }, [mode, onChange])

  const handleDateSelect = (nextDate) => {
    const nextTime = clampTimeToBounds(nextDate, draftTime, minDateTime, maxDateTime)
    setDraftDate(nextDate)
    setDraftTime(nextTime)
    applyValue(nextDate, nextTime)
  }

  const handleTimeChange = (event) => {
    const nextTime = clampTimeToBounds(draftDate, event.target.value, minDateTime, maxDateTime)
    setDraftTime(nextTime)
    applyValue(draftDate, nextTime)
  }

  const clearValue = (event) => {
    event.stopPropagation()
    const now = `${pad(new Date().getHours())}:${pad(new Date().getMinutes())}`
    setDraftDate('')
    setDraftTime(now)
    onChange?.('')
  }

  const prevMonth = () => {
    if (viewMonth === 0) {
      setViewMonth(11)
      setViewYear((current) => current - 1)
      return
    }

    setViewMonth((current) => current - 1)
  }

  const nextMonth = () => {
    if (viewMonth === 11) {
      setViewMonth(0)
      setViewYear((current) => current + 1)
      return
    }

    setViewMonth((current) => current + 1)
  }

  const triggerClass = [
    'relative flex h-[42px] w-full cursor-pointer items-center gap-2 rounded-xl border px-3.5 text-sm transition-all duration-150',
    'bg-white/[0.06] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-0',
    error
      ? 'border-red-500/60 focus-visible:ring-red-500/40'
      : open
        ? 'border-accent/50 ring-2 ring-accent/40'
        : 'border-white/12 hover:border-white/22',
    disabled ? 'pointer-events-none cursor-not-allowed opacity-50' : '',
    className,
  ].join(' ')

  const selectedDate = parseDatePart(draftDate)
  const minDateTimeParts = splitDateTime(minDateTime)
  const maxDateTimeParts = splitDateTime(maxDateTime)
  const effectiveMinDate = maxDateValue(minDate, minDateTimeParts.date)
  const effectiveMaxDate = minDateValue(maxDate, maxDateTimeParts.date)
  const minTime = draftDate && draftDate === minDateTimeParts.date ? minDateTimeParts.time || undefined : undefined
  const maxTime = draftDate && draftDate === maxDateTimeParts.date ? maxDateTimeParts.time || undefined : undefined

  return (
    <div className="flex flex-col gap-1.5">
      {label && (
        <label htmlFor={inputId} className="text-sm font-medium text-white/85 select-none">
          {label}
          {required && <span className="ml-1 text-red-400">*</span>}
        </label>
      )}

      <div
        ref={triggerRef}
        id={inputId}
        role="button"
        tabIndex={disabled ? -1 : 0}
        aria-label={label ?? effectivePlaceholder}
        className={triggerClass}
        onClick={openPicker}
        onKeyDown={(event) => {
          if (event.key === 'Enter' || event.key === ' ') {
            event.preventDefault()
            openPicker()
          }
        }}
      >
        <svg width="14" height="14" viewBox="0 0 14 14" fill="none" className="shrink-0 text-slate-500" aria-hidden="true">
          <rect x="1" y="2.5" width="12" height="10.5" rx="1.5" stroke="currentColor" strokeWidth="1.3" />
          <path d="M1 6h12" stroke="currentColor" strokeWidth="1.3" />
          <path d="M4 1v3M10 1v3" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" />
          <circle cx="4.5" cy="9" r="0.75" fill="currentColor" />
          <circle cx="7" cy="9" r="0.75" fill="currentColor" />
          <circle cx="9.5" cy="9" r="0.75" fill="currentColor" />
        </svg>

        <span className={`flex-1 truncate ${value ? 'text-white' : 'text-slate-500'}`}>
          {value ? formatDisplay(value) : effectivePlaceholder}
        </span>

        {clearable && value && (
          <button
            type="button"
            tabIndex={-1}
            onClick={clearValue}
            className="flex h-5 w-5 items-center justify-center rounded text-slate-500 transition-colors hover:text-white"
            aria-label="Clear date and time"
          >
            <svg width="10" height="10" viewBox="0 0 10 10" fill="none" aria-hidden="true">
              <path d="M1 1l8 8M9 1L1 9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
            </svg>
          </button>
        )}
      </div>

      {error && <p role="alert" className="text-xs text-red-400">{error}</p>}
      {!error && hint && <p className="text-xs text-slate-500">{hint}</p>}

      {open && createPortal(
        <div
          id={panelId}
          className="fixed z-[500] overflow-hidden rounded-2xl border border-white/12 bg-nova-900 shadow-2xl shadow-black/50"
          style={{ top: dropPos.top, left: dropPos.left, width: dropPos.width }}
        >
          <div className="flex items-center justify-between gap-2 px-3 pt-3">
            <button
              type="button"
              onClick={prevMonth}
              className="flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-white/8 hover:text-white"
              aria-label="Previous month"
            >
              <svg width="10" height="10" viewBox="0 0 10 10" fill="none" aria-hidden="true">
                <path d="M7 1L3 5l4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </button>

            <div className="flex min-w-0 flex-1 items-center justify-center gap-2">
              <span className="whitespace-nowrap text-sm font-semibold text-white">{MONTH_NAMES[viewMonth]}</span>
              <div className="flex items-center gap-1 rounded-xl border border-white/10 bg-white/[0.04] px-1 py-1">
                <button
                  type="button"
                  onClick={() => setViewYear((current) => Math.max(YEAR_MIN, current - 1))}
                  disabled={viewYear <= YEAR_MIN}
                  className="flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-white/8 hover:text-white disabled:cursor-not-allowed disabled:opacity-30"
                  aria-label="Previous year"
                >
                  <i className="fa-solid fa-minus text-[11px]" aria-hidden="true" />
                </button>

                <span className="min-w-[72px] px-2 text-center text-sm font-semibold text-white">{viewYear}</span>

                <button
                  type="button"
                  onClick={() => setViewYear((current) => Math.min(YEAR_MAX, current + 1))}
                  disabled={viewYear >= YEAR_MAX}
                  className="flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-white/8 hover:text-white disabled:cursor-not-allowed disabled:opacity-30"
                  aria-label="Next year"
                >
                  <i className="fa-solid fa-plus text-[11px]" aria-hidden="true" />
                </button>
              </div>
            </div>

            <button
              type="button"
              onClick={nextMonth}
              className="flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-white/8 hover:text-white"
              aria-label="Next month"
            >
              <svg width="10" height="10" viewBox="0 0 10 10" fill="none" aria-hidden="true">
                <path d="M3 1l4 4-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </button>
          </div>

          <CalendarGrid
            year={viewYear}
            month={viewMonth}
            selectedDate={selectedDate}
            onSelect={handleDateSelect}
            minDate={effectiveMinDate}
            maxDate={effectiveMaxDate}
          />

          <div className="border-t border-white/8 px-4 py-3">
            <div className={`grid gap-3 ${mode === 'date' ? '' : 'sm:grid-cols-[minmax(0,1fr)_7rem] sm:items-end'}`}>
              <div>
                <div className="mb-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Selected date</div>
                <div className="rounded-xl border border-white/10 bg-white/[0.04] px-3 py-2 text-sm text-white">
                  {draftDate ? formatDisplay(draftDate) : 'Pick a day'}
                </div>
              </div>

              {mode !== 'date' ? (
                <label className="grid gap-1.5 text-sm text-slate-300">
                  <span className="text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Time</span>
                  <input
                    type="time"
                    value={draftTime}
                    onChange={handleTimeChange}
                    min={minTime}
                    max={maxTime}
                    className="rounded-xl border border-white/10 bg-white/[0.04] px-3 py-2 text-white outline-none transition focus:border-accent/50 focus:ring-2 focus:ring-accent/40"
                  />
                </label>
              ) : null}
            </div>

            <div className="mt-3 flex items-center justify-between">
              <button
                type="button"
                onClick={() => handleDateSelect(toISODate(new Date()))}
                className="text-xs font-medium text-accent transition-colors hover:text-accent/80"
              >
                Today
              </button>

              <button
                type="button"
                onClick={() => setOpen(false)}
                className="rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-medium text-white transition hover:bg-white/[0.08]"
              >
                Done
              </button>
            </div>
          </div>
        </div>,
        document.body,
      )}
    </div>
  )
}