import { useState, useRef, useEffect, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { createPortal } from 'react-dom'
import { IconChevronDownFilled } from '@tabler/icons-react'

interface Props {
  value: string // YYYY-MM-DD
  onChange: (value: string) => void
  className?: string
}

// i18n translations loaded in component

function parse(s: string) {
  const [y, m, d] = s.split('-').map(Number)
  return { year: y, month: m, day: d }
}

function fmt(y: number, m: number, d: number) {
  return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`
}

function daysInMonth(y: number, m: number) {
  return new Date(y, m, 0).getDate()
}

function firstDayOfWeek(y: number, m: number) {
  const d = new Date(y, m - 1, 1).getDay()
  return d === 0 ? 6 : d - 1
}

export default function DatePicker({ value, onChange, className }: Props) {
  const { t } = useTranslation()
  // SAFETY: these i18n keys return arrays only when returnObjects is set, so each value is a string[]
  const MONTHS = t('datepicker.months', { returnObjects: true }) as string[]
  // SAFETY: same contract as MONTHS - the returnObjects flag makes this i18n key return a string[]
  const WEEKDAYS = t('datepicker.weekdays', { returnObjects: true }) as string[]
  const [open, setOpen] = useState(false)
  const triggerRef = useRef<HTMLButtonElement>(null)
  const calRef = useRef<HTMLDivElement>(null)
  const dayRefs = useRef<(HTMLButtonElement | null)[]>([])
  const [focusDay, setFocusDay] = useState<number | null>(null)
  const { year, month } = parse(value)
  const [viewYear, setViewYear] = useState(year)
  const [viewMonth, setViewMonth] = useState(month)
  const [pos, setPos] = useState({ top: 0, left: 0 })

  // the day of the selected value, when it falls inside the current view
  const selected = parse(value)
  const selectedInView =
    selected.year === viewYear && selected.month === viewMonth ? selected.day : 1

  const updatePos = useCallback(() => {
    if (!triggerRef.current) return
    const rect = triggerRef.current.getBoundingClientRect()
    setPos({ top: rect.bottom + 4, left: rect.left })
  }, [])

  useEffect(() => {
    if (!open) return
    updatePos()
    function onDown(e: MouseEvent) {
      const target = e.target
      if (target instanceof Node && calRef.current && !calRef.current.contains(target) &&
          triggerRef.current && !triggerRef.current.contains(target)) {
        setOpen(false)
      }
    }
    function onKey(e: KeyboardEvent) {
      if (e.key === 'Escape') { setFocusDay(null); setOpen(false); triggerRef.current?.focus() }
    }
    window.addEventListener('scroll', updatePos, true)
    document.addEventListener('mousedown', onDown)
    document.addEventListener('keydown', onKey)
    return () => {
      window.removeEventListener('scroll', updatePos, true)
      document.removeEventListener('mousedown', onDown)
      document.removeEventListener('keydown', onKey)
    }
  }, [open, updatePos])

  const prevMonth = () => {
    if (viewMonth === 1) { setViewMonth(12); setViewYear(viewYear - 1) }
    else setViewMonth(viewMonth - 1)
  }
  const nextMonth = () => {
    if (viewMonth === 12) { setViewMonth(1); setViewYear(viewYear + 1) }
    else setViewMonth(viewMonth + 1)
  }

  const days = daysInMonth(viewYear, viewMonth)
  const offset = firstDayOfWeek(viewYear, viewMonth)
  const today = new Date().toISOString().split('T')[0]

  const pick = (d: number) => {
    onChange(fmt(viewYear, viewMonth, d))
    setFocusDay(null)
    setOpen(false)
  }

  const focusDayRef = (d: number) => {
    setFocusDay(d)
    dayRefs.current[d - 1]?.focus()
  }

  const onGridKeyDown = (e: { key: string; preventDefault(): void }) => {
    const cur = focusDay != null ? focusDay : selectedInView
    let next = cur
    if (e.key === 'ArrowRight') next = cur + 1
    else if (e.key === 'ArrowLeft') next = cur - 1
    else if (e.key === 'ArrowDown') next = cur + 7
    else if (e.key === 'ArrowUp') next = cur - 7
    else return
    e.preventDefault()
    if (next < 1 || next > days) return
    focusDayRef(next)
  }

  useEffect(() => {
    if (!open) return
    const d = focusDay != null && focusDay >= 1 && focusDay <= days ? focusDay : selectedInView
    dayRefs.current[d - 1]?.focus()
  }, [open, days, selectedInView])

  return (
    <div className={`cv-datepicker ${className || ''}`}>
      <button className="cv-select-trigger" ref={triggerRef} onClick={() => setOpen(!open)} type="button"
        aria-haspopup="dialog" aria-expanded={open}>
        <span>{value}</span>
        <IconChevronDownFilled size={10} style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform 150ms' }} />
      </button>
      {open && createPortal(
        <div className="cv-calendar" ref={calRef} style={{ top: pos.top, left: pos.left }}
          role="dialog" aria-label={t('datepicker.calendarLabel', { month: MONTHS[viewMonth - 1], year: viewYear })}>
          <div className="cv-cal-header">
            <button className="cv-cal-nav" onClick={prevMonth} type="button" aria-label={t('datepicker.prevMonth')}>&lt;</button>
            <span className="cv-cal-title">{MONTHS[viewMonth - 1]} {viewYear}</span>
            <button className="cv-cal-nav" onClick={nextMonth} type="button" aria-label={t('datepicker.nextMonth')}>&gt;</button>
          </div>
          <div className="cv-cal-days" role="grid">
            {WEEKDAYS.map(d => <span key={d} className="cv-cal-dayname">{d}</span>)}
          </div>
          <div className="cv-cal-grid" role="grid" onKeyDown={onGridKeyDown}>
            {Array.from({ length: offset }, (_, i) => <span key={`pad-${i}`} />)}
            {Array.from({ length: days }, (_, i) => {
              const d = i + 1
              const dateStr = fmt(viewYear, viewMonth, d)
              const isSelected = dateStr === value
              const isToday = dateStr === today
              return (
                <button
                  key={d}
                  ref={el => { dayRefs.current[d - 1] = el }}
                  className={`cv-cal-day ${isSelected ? 'cv-cal-day-selected' : ''} ${isToday ? 'cv-cal-day-today' : ''}`}
                  onClick={() => pick(d)}
                  onFocus={() => setFocusDay(d)}
                  type="button"
                  aria-selected={isSelected}
                >
                  {d}
                </button>
              )
            })}
          </div>
        </div>,
        document.body,
      )}
    </div>
  )
}
