import * as React from 'react';
import { createPortal } from 'react-dom';
import { Calendar, ChevronLeft, ChevronRight, Clock } from 'lucide-react';
import { cn } from '@/lib/utils';

// ── Browser detection (stable — checked once at module load) ──

const isFirefox = typeof navigator !== 'undefined' && /Firefox\//i.test(navigator.userAgent);

// ── Helpers ──

function getDaysInMonth(year: number, month: number): number {
  return new Date(year, month + 1, 0).getDate();
}

function getFirstDayOfWeek(year: number, month: number): number {
  return new Date(year, month, 1).getDay();
}

function pad(n: number): string {
  return n.toString().padStart(2, '0');
}

function formatDate(y: number, m: number, d: number): string {
  return `${y}-${pad(m + 1)}-${pad(d)}`;
}

function to12Hour(h24: number): { hour: number; ampm: 'AM' | 'PM' } {
  if (h24 === 0) return { hour: 12, ampm: 'AM' };
  if (h24 < 12) return { hour: h24, ampm: 'AM' };
  if (h24 === 12) return { hour: 12, ampm: 'PM' };
  return { hour: h24 - 12, ampm: 'PM' };
}

function to24Hour(h12: number, ampm: string): number {
  const isPM = ampm.toUpperCase() === 'PM';
  if (h12 === 12) return isPM ? 12 : 0;
  return isPM ? h12 + 12 : h12;
}

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

const DAY_HEADERS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
const HOUR_OPTIONS = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];

// ── Props ──

export interface DatePickerProps {
  variant?: 'button' | 'input';
  mode?: 'date' | 'datetime';
  value: string;
  onChange: (value: string) => void;
  onBlur?: () => void;
  onKeyDown?: (e: React.KeyboardEvent) => void;
  id?: string;
  name?: string;
  autoComplete?: string;
  required?: boolean;
  disabled?: boolean;
  className?: string;
  placeholder?: string;
  min?: string;
  max?: string;
  yearRange?: [number, number];
}

// ── Component ──

const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
  (
    {
      variant = 'button',
      mode = 'date',
      value,
      onChange,
      onBlur,
      onKeyDown,
      id,
      name,
      autoComplete,
      required,
      disabled,
      className,
      placeholder,
      min,
      max,
      yearRange,
    },
    ref
  ) => {
    const currentYear = new Date().getFullYear();
    const [startYear, endYear] = yearRange ?? [1900, currentYear + 20];

    // Parse ISO value into parts
    const parseDateValue = () => {
      if (!value) return null;
      const parts = value.split('T');
      const dateParts = parts[0]?.split('-');
      if (!dateParts || dateParts.length !== 3) return null;
      const y = parseInt(dateParts[0], 10);
      const m = parseInt(dateParts[1], 10) - 1;
      const d = parseInt(dateParts[2], 10);
      if (isNaN(y) || isNaN(m) || isNaN(d)) return null;
      const timeParts = parts[1]?.split(':');
      const hour = timeParts ? parseInt(timeParts[0], 10) : 0;
      const minute = timeParts ? parseInt(timeParts[1], 10) : 0;
      return { year: y, month: m, day: d, hour, minute };
    };

    const parsed = parseDateValue();
    const today = new Date();

    const [open, setOpen] = React.useState(false);
    const [viewYear, setViewYear] = React.useState(parsed?.year ?? today.getFullYear());
    const [viewMonth, setViewMonth] = React.useState(parsed?.month ?? today.getMonth());
    const [hour, setHour] = React.useState(parsed?.hour ?? 0);
    const [minute, setMinute] = React.useState(parsed?.minute ?? 0);

    // Refs
    const triggerRef = React.useRef<HTMLButtonElement>(null);
    const wrapperRef = React.useRef<HTMLDivElement>(null);
    const inputElRef = React.useRef<HTMLInputElement>(null);
    const popupRef = React.useRef<HTMLDivElement>(null);
    const blurTimeoutRef = React.useRef<ReturnType<typeof setTimeout>>();

    const [pos, setPos] = React.useState<{ top: number; left: number }>({ top: 0, left: 0 });

    React.useImperativeHandle(ref, () => triggerRef.current!);

    // Sync popup view state when value changes (only when popup is closed)
    React.useEffect(() => {
      if (open) return;
      const p = parseDateValue();
      if (p) {
        setViewYear(p.year);
        setViewMonth(p.month);
        setHour(p.hour);
        setMinute(p.minute);
      }
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [value, open]);

    // Position popup
    // Firefox + input variant falls through to button variant, so use triggerRef
    const usesNativeInput = variant === 'input' && !isFirefox;
    const updatePosition = React.useCallback(() => {
      const el = usesNativeInput ? wrapperRef.current : triggerRef.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const popupHeight = mode === 'datetime' ? 370 : 320;
      const spaceBelow = window.innerHeight - rect.bottom;
      const flipped = spaceBelow < popupHeight && rect.top > popupHeight;

      setPos({
        top: flipped ? rect.top - popupHeight - 4 : rect.bottom + 4,
        left: Math.min(rect.left, window.innerWidth - 290),
      });
    }, [mode, usesNativeInput]);

    React.useEffect(() => {
      if (!open) return;
      updatePosition();
      window.addEventListener('scroll', updatePosition, true);
      window.addEventListener('resize', updatePosition);
      return () => {
        window.removeEventListener('scroll', updatePosition, true);
        window.removeEventListener('resize', updatePosition);
      };
    }, [open, updatePosition]);

    const closePopup = React.useCallback(
      (refocusTrigger = true) => {
        setOpen(false);
        if (!usesNativeInput) {
          onBlur?.();
        } else if (refocusTrigger) {
          setTimeout(() => inputElRef.current?.focus(), 0);
        }
      },
      [usesNativeInput, onBlur]
    );

    // Dismiss on click outside
    React.useEffect(() => {
      if (!open) return;
      const handler = (e: MouseEvent) => {
        if (popupRef.current?.contains(e.target as Node)) return;
        if (!usesNativeInput && triggerRef.current?.contains(e.target as Node)) return;
        if (usesNativeInput && wrapperRef.current?.contains(e.target as Node)) return;
        closePopup(false);
      };
      document.addEventListener('mousedown', handler);
      return () => document.removeEventListener('mousedown', handler);
    }, [open, usesNativeInput, closePopup]);

    // Dismiss on Escape
    React.useEffect(() => {
      if (!open) return;
      const handler = (e: KeyboardEvent) => {
        if (e.key === 'Escape') {
          e.stopPropagation();
          closePopup(true);
        }
      };
      document.addEventListener('keydown', handler);
      return () => document.removeEventListener('keydown', handler);
    }, [open, closePopup]);

    // Input variant: smart blur — only fires when focus truly leaves the component
    const handleInputBlur = React.useCallback(() => {
      if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
      blurTimeoutRef.current = setTimeout(() => {
        const active = document.activeElement;
        if (
          popupRef.current?.contains(active) ||
          wrapperRef.current?.contains(active)
        ) return;
        onBlur?.();
      }, 10);
    }, [onBlur]);

    React.useEffect(() => {
      return () => {
        if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
      };
    }, []);

    const openPopup = () => {
      if (disabled) return;
      const p = parseDateValue();
      if (p) {
        setViewYear(p.year);
        setViewMonth(p.month);
        setHour(p.hour);
        setMinute(p.minute);
      }
      setOpen(true);
    };

    const togglePopup = () => {
      if (open) closePopup(true);
      else openPopup();
    };

    // Min/Max boundaries
    const minDate = min ? new Date(min + 'T00:00:00') : null;
    const maxDate = max ? new Date(max + 'T00:00:00') : null;

    const isDayDisabled = (y: number, m: number, d: number) => {
      const date = new Date(y, m, d);
      if (minDate && date < minDate) return true;
      if (maxDate && date > maxDate) return true;
      return false;
    };

    const selectDay = (day: number) => {
      if (isDayDisabled(viewYear, viewMonth, day)) return;
      const dateStr = formatDate(viewYear, viewMonth, day);
      if (mode === 'datetime') {
        onChange(`${dateStr}T${pad(hour)}:${pad(minute)}`);
      } else {
        onChange(dateStr);
        closePopup(true);
      }
    };

    const handleTimeChange = (newHour: number, newMinute: number) => {
      setHour(newHour);
      setMinute(newMinute);
      if (parsed) {
        const dateStr = formatDate(parsed.year, parsed.month, parsed.day);
        onChange(`${dateStr}T${pad(newHour)}:${pad(newMinute)}`);
      }
    };

    const prevMonth = () => {
      if (viewMonth === 0) { setViewMonth(11); setViewYear((y) => y - 1); }
      else setViewMonth((m) => m - 1);
    };

    const nextMonth = () => {
      if (viewMonth === 11) { setViewMonth(0); setViewYear((y) => y + 1); }
      else setViewMonth((m) => m + 1);
    };

    // Calendar grid
    const daysInMonth = getDaysInMonth(viewYear, viewMonth);
    const firstDay = getFirstDayOfWeek(viewYear, viewMonth);
    const cells: (number | null)[] = [];
    for (let i = 0; i < firstDay; i++) cells.push(null);
    for (let d = 1; d <= daysInMonth; d++) cells.push(d);

    const isTodayDay = (d: number) =>
      d === today.getDate() && viewMonth === today.getMonth() && viewYear === today.getFullYear();

    const isSelected = (d: number) =>
      parsed !== null && d === parsed.day && viewMonth === parsed.month && viewYear === parsed.year;

    // 12-hour display values for time selectors
    const { hour: h12, ampm: currentAmpm } = to12Hour(hour);

    // Button variant display text
    const displayText = (() => {
      if (!parsed) return '';
      const monthName = MONTH_NAMES[parsed.month];
      const base = `${monthName} ${parsed.day}, ${parsed.year}`;
      if (mode === 'datetime') {
        const { hour: dh, ampm: da } = to12Hour(parsed.hour);
        return `${base}  ${dh}:${pad(parsed.minute)} ${da}`;
      }
      return base;
    })();

    // Year options
    const yearOptions: number[] = [];
    for (let y = startYear; y <= endYear; y++) yearOptions.push(y);

    // ── Shared popup ──
    const popup = open
      ? createPortal(
          <div
            ref={popupRef}
            onMouseDown={(e) => e.stopPropagation()}
            style={{ position: 'fixed', top: pos.top, left: pos.left, zIndex: 60 }}
            className="w-[280px] rounded-lg border border-input bg-card shadow-lg animate-fade-in"
          >
            {/* Month/Year nav */}
            <div className="flex items-center justify-between px-3 pt-3 pb-2">
              <button type="button" onClick={prevMonth} className="p-1 rounded-md hover:bg-accent/10 transition-colors">
                <ChevronLeft className="h-4 w-4" />
              </button>
              <div className="flex items-center gap-1.5">
                <select
                  value={viewMonth}
                  onChange={(e) => setViewMonth(parseInt(e.target.value, 10))}
                  className="appearance-none bg-transparent text-sm font-medium cursor-pointer hover:text-accent focus:outline-none pr-1"
                >
                  {MONTH_NAMES.map((n, i) => (
                    <option key={i} value={i} className="bg-card text-foreground">{n}</option>
                  ))}
                </select>
                <select
                  value={viewYear}
                  onChange={(e) => setViewYear(parseInt(e.target.value, 10))}
                  className="appearance-none bg-transparent text-sm font-medium cursor-pointer hover:text-accent focus:outline-none"
                >
                  {yearOptions.map((y) => (
                    <option key={y} value={y} className="bg-card text-foreground">{y}</option>
                  ))}
                </select>
              </div>
              <button type="button" onClick={nextMonth} className="p-1 rounded-md hover:bg-accent/10 transition-colors">
                <ChevronRight className="h-4 w-4" />
              </button>
            </div>

            {/* Day headers */}
            <div className="grid grid-cols-7 px-3 pb-1">
              {DAY_HEADERS.map((d) => (
                <div key={d} className="text-center text-[11px] font-medium text-muted-foreground py-1">{d}</div>
              ))}
            </div>

            {/* Day grid */}
            <div className="grid grid-cols-7 px-3 pb-3">
              {cells.map((day, i) =>
                day === null ? (
                  <div key={`empty-${i}`} />
                ) : (
                  <button
                    key={day}
                    type="button"
                    disabled={isDayDisabled(viewYear, viewMonth, day)}
                    onClick={() => selectDay(day)}
                    className={cn(
                      'h-8 w-full rounded-md text-sm transition-colors',
                      'hover:bg-accent/10 focus:outline-none focus-visible:ring-1 focus-visible:ring-ring',
                      isSelected(day) && 'bg-accent text-accent-foreground font-medium',
                      isTodayDay(day) && !isSelected(day) && 'border border-accent/50 text-accent',
                      isDayDisabled(viewYear, viewMonth, day) &&
                        'opacity-30 cursor-not-allowed hover:bg-transparent'
                    )}
                  >
                    {day}
                  </button>
                )
              )}
            </div>

            {/* Time selectors — 12-hour with AM/PM */}
            {mode === 'datetime' && (
              <div className="flex items-center gap-1.5 px-3 pb-3 border-t border-border pt-2">
                <Clock className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
                <select
                  value={h12}
                  onChange={(e) => handleTimeChange(to24Hour(parseInt(e.target.value, 10), currentAmpm), minute)}
                  className="w-14 appearance-none bg-secondary rounded-md px-2 py-1 text-sm text-center focus:outline-none focus-visible:ring-1 focus-visible:ring-ring cursor-pointer"
                >
                  {HOUR_OPTIONS.map((h) => (
                    <option key={h} value={h} className="bg-card text-foreground">{h}</option>
                  ))}
                </select>
                <span className="text-muted-foreground font-medium">:</span>
                <select
                  value={minute}
                  onChange={(e) => handleTimeChange(hour, parseInt(e.target.value, 10))}
                  className="w-14 appearance-none bg-secondary rounded-md px-2 py-1 text-sm text-center focus:outline-none focus-visible:ring-1 focus-visible:ring-ring cursor-pointer"
                >
                  {Array.from({ length: 60 }, (_, i) => (
                    <option key={i} value={i} className="bg-card text-foreground">{pad(i)}</option>
                  ))}
                </select>
                <select
                  value={currentAmpm}
                  onChange={(e) => handleTimeChange(to24Hour(h12, e.target.value), minute)}
                  className="w-16 appearance-none bg-secondary rounded-md px-2 py-1 text-sm text-center focus:outline-none focus-visible:ring-1 focus-visible:ring-ring cursor-pointer"
                >
                  <option value="AM" className="bg-card text-foreground">AM</option>
                  <option value="PM" className="bg-card text-foreground">PM</option>
                </select>
                <button
                  type="button"
                  onClick={() => closePopup(true)}
                  className="ml-auto px-2 py-1 text-xs font-medium rounded-md bg-accent text-accent-foreground hover:bg-accent/90 transition-colors"
                >
                  Done
                </button>
              </div>
            )}
          </div>,
          document.body
        )
      : null;

    // ── Input variant (Chromium only) ──
    // Firefox: falls through to the button variant below because Firefox has no
    // CSS pseudo-element to hide its native calendar icon (Mozilla bug 1830890).
    // Chromium: uses native type="date"/"datetime-local" for segmented editing UX,
    // with the native icon hidden via CSS in index.css (.datepicker-wrapper rule).
    if (variant === 'input' && !isFirefox) {
      return (
        <>
          <div ref={wrapperRef} className="datepicker-wrapper relative">
            <input
              ref={inputElRef}
              type={mode === 'datetime' ? 'datetime-local' : 'date'}
              id={id}
              name={name}
              autoComplete={autoComplete}
              value={value}
              onChange={(e) => onChange(e.target.value)}
              onBlur={handleInputBlur}
              onKeyDown={(e) => {
                if (open && e.key === 'Enter') {
                  e.preventDefault();
                  return;
                }
                onKeyDown?.(e);
              }}
              required={required}
              disabled={disabled}
              min={min}
              max={max}
              className={cn(
                'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-9 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
                className
              )}
            />
            <button
              type="button"
              disabled={disabled}
              onMouseDown={(e) => e.preventDefault()}
              onClick={togglePopup}
              className="absolute right-px top-px bottom-px w-9 flex items-center justify-center rounded-r-md bg-background hover:bg-accent/10 transition-colors disabled:opacity-50"
              tabIndex={-1}
              aria-label="Open calendar"
            >
              <Calendar className="h-4 w-4 opacity-70" />
            </button>
          </div>
          {popup}
        </>
      );
    }

    // ── Button variant: non-editable trigger (registration DOB) ──
    return (
      <>
        <input type="hidden" name={name} autoComplete={autoComplete} value={value} required={required} />
        {required && (
          <input
            tabIndex={-1}
            aria-hidden
            className="absolute w-0 h-0 opacity-0 pointer-events-none"
            value={value}
            required
            onChange={() => {}}
            style={{ position: 'absolute' }}
          />
        )}

        <button
          ref={triggerRef}
          type="button"
          id={id}
          disabled={disabled}
          onClick={togglePopup}
          onKeyDown={(e) => {
            if (e.key === 'Enter' || e.key === ' ') {
              e.preventDefault();
              togglePopup();
            }
            if (open) return;
            onKeyDown?.(e);
          }}
          className={cn(
            'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
            !value && 'text-muted-foreground',
            className
          )}
        >
          <span className="truncate">
            {displayText || placeholder || (mode === 'datetime' ? 'Pick date & time' : 'Pick a date')}
          </span>
          <Calendar className="h-4 w-4 shrink-0 opacity-70" />
        </button>

        {popup}
      </>
    );
  }
);
DatePicker.displayName = 'DatePicker';

export { DatePicker };
