import { jsxs, jsx } from 'react/jsx-runtime';
import React__default, { useState, useEffect, useMemo, useCallback } from 'react';
import { useReducedMotion, motion } from 'motion/react';
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
import { calendarTheme } from './Calendar.theme.js';
import { calendarAnimations } from './Calendar.animations.js';
import { cn } from '../../../utils/cn.js';
import { Select } from '../../forms/Select/Select.js';
import '../../forms/Select/Select.theme.js';
import { getMonthName, isDateDisabled as isDateDisabled$1, isSameDay } from './calendar.utils.js';
import { Button } from '../Button/Button.js';

/**
 * Constants for date utilities
 */
const DEFAULT_MONTHS = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December",
];
const ABBREVIATED_MONTHS = [
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec",
];
/**
 * Date range utility (specific to Calendar component)
 */
const isDateInRange = (date, start, end) => {
    if (!start || !end)
        return false;
    const dateTime = date.getTime();
    const startTime = start.getTime();
    const endTime = end.getTime();
    return dateTime > startTime && dateTime < endTime;
};
/**
 * Wrapper for isDateDisabled to support custom disabled function
 */
const isDateDisabled = (date, minDate, maxDate, customDisabled) => {
    // Use shared utility for min/max dates
    if (isDateDisabled$1(date, minDate, maxDate))
        return true;
    // Add custom disabled function check
    if (customDisabled && customDisabled(date))
        return true;
    return false;
};
/**
 * Generate calendar days for a given month and year with range support
 */
const generateCalendarDays = (year, month, selectedValue, mode = 'single', today, minDate, maxDate, customDisabled) => {
    const firstDayOfMonth = new Date(year, month, 1);
    const lastDayOfMonth = new Date(year, month + 1, 0);
    const startDate = new Date(firstDayOfMonth);
    // Adjust to start from Monday (1) instead of Sunday (0)
    const dayOfWeek = (firstDayOfMonth.getDay() + 6) % 7;
    startDate.setDate(startDate.getDate() - dayOfWeek);
    const days = [];
    const currentDate = new Date(startDate);
    // Calculate minimum weeks needed to show all days of the month
    const endDate = new Date(lastDayOfMonth);
    const endDayOfWeek = (endDate.getDay() + 6) % 7;
    const daysAfterMonth = 6 - endDayOfWeek;
    const totalDays = dayOfWeek + lastDayOfMonth.getDate() + daysAfterMonth;
    const weeksNeeded = Math.ceil(totalDays / 7);
    const daysToGenerate = weeksNeeded * 7;
    // Determine selected date(s) based on mode
    const selectedDate = mode === 'single' && selectedValue instanceof Date ? selectedValue : undefined;
    const rangeStart = mode === 'range' && selectedValue && typeof selectedValue === 'object' && 'start' in selectedValue ? selectedValue.start : null;
    const rangeEnd = mode === 'range' && selectedValue && typeof selectedValue === 'object' && 'end' in selectedValue ? selectedValue.end : null;
    // Generate only the needed number of days to fill complete weeks
    for (let i = 0; i < daysToGenerate; i++) {
        const date = new Date(currentDate);
        const isCurrentMonth = date.getMonth() === month;
        const isToday = today ? isSameDay(date, today) : false;
        const isSelected = mode === 'single' && selectedDate ? isSameDay(date, selectedDate) : false;
        const disabled = isDateDisabled(date, minDate, maxDate, customDisabled);
        // Range-specific properties
        const isRangeStart = mode === 'range' && rangeStart ? isSameDay(date, rangeStart) : false;
        const isRangeEnd = mode === 'range' && rangeEnd ? isSameDay(date, rangeEnd) : false;
        const isInRange = mode === 'range' ? isDateInRange(date, rangeStart, rangeEnd) : false;
        days.push({
            date,
            isCurrentMonth,
            isToday,
            isSelected,
            isRangeStart,
            isRangeEnd,
            isInRange,
            isDisabled: disabled,
        });
        currentDate.setDate(currentDate.getDate() + 1);
    }
    return days;
};
const Calendar = React__default.forwardRef(({ mode = 'single', value, defaultValue, onChange, minDate, maxDate, isDateDisabled: customIsDateDisabled, showToday = true, selectTodayByDefault = false, weekdays = ["M", "T", "W", "T", "F", "S", "S"], months, size = "md", headerMode, enableMonthDropdown = true, enableYearDropdown = true, yearRange, viewDate: externalViewDate, onViewDateChange, onNavigatePrev, onNavigateNext, showOtherMonthDays = true, className, ...props }, ref) => {
    // Determine initial view date from value/defaultValue
    const getInitialViewDate = () => {
        if (value) {
            if (value instanceof Date)
                return value;
            if (value.start)
                return value.start;
        }
        if (defaultValue) {
            if (defaultValue instanceof Date)
                return defaultValue;
            if (defaultValue.start)
                return defaultValue.start;
        }
        return new Date();
    };
    // Internal state for current view (month/year) - use external if provided
    const [internalViewDate, setInternalViewDate] = useState(getInitialViewDate);
    const viewDate = externalViewDate || internalViewDate;
    // Internal state for selected value (uncontrolled mode)
    const [internalValue, setInternalValue] = useState(() => {
        if (defaultValue)
            return defaultValue;
        if (selectTodayByDefault && !value && mode === 'single') {
            const today = new Date();
            if (!isDateDisabled(today, minDate, maxDate, customIsDateDisabled)) {
                return today;
            }
        }
        return undefined;
    });
    // Track range selection state (for range mode)
    const [rangeSelectionState, setRangeSelectionState] = useState('start');
    // Determine the selected value (controlled vs uncontrolled)
    const selectedValue = value !== undefined ? value : internalValue;
    // Keep range selection state in sync with current selection to avoid misinterpreting clicks
    useEffect(() => {
        if (mode !== 'range')
            return;
        const currentRange = selectedValue && typeof selectedValue === 'object' && 'start' in selectedValue
            ? selectedValue
            : { start: null, end: null };
        if (currentRange.start && !currentRange.end) {
            setRangeSelectionState('end');
        }
        else {
            setRangeSelectionState('start');
        }
    }, [mode, selectedValue]);
    // Current month and year being displayed
    const currentYear = viewDate.getFullYear();
    const currentMonth = viewDate.getMonth();
    // Reduced motion support
    const shouldReduceMotion = useReducedMotion();
    // Resolve header display mode with backward compatibility
    const resolvedHeaderMode = useMemo(() => {
        // If headerMode is explicitly set, use it
        if (headerMode)
            return headerMode;
        // Backward compatibility: infer from enableMonthDropdown and enableYearDropdown
        if (!enableMonthDropdown && !enableYearDropdown)
            return 'static';
        if (enableMonthDropdown && enableYearDropdown)
            return 'dropdown';
        return 'mixed';
    }, [headerMode, enableMonthDropdown, enableYearDropdown]);
    // Navigation boundary detection - disable arrows when at min/max limits
    const canNavigateToPrev = useMemo(() => {
        if (!minDate)
            return true;
        const prevMonth = new Date(currentYear, currentMonth - 1, 1);
        const minMonth = new Date(minDate.getFullYear(), minDate.getMonth(), 1);
        return prevMonth >= minMonth;
    }, [minDate, currentYear, currentMonth]);
    const canNavigateToNext = useMemo(() => {
        if (!maxDate)
            return true;
        const nextMonth = new Date(currentYear, currentMonth + 1, 1);
        const maxMonth = new Date(maxDate.getFullYear(), maxDate.getMonth(), 1);
        return nextMonth <= maxMonth;
    }, [maxDate, currentYear, currentMonth]);
    // Generate month options - filter based on minDate/maxDate
    const monthNames = months || DEFAULT_MONTHS;
    const monthOptions = useMemo(() => {
        return monthNames
            .map((monthName, index) => ({
            id: index,
            value: index.toString(),
            label: monthName,
        }))
            .filter((option) => {
            const monthIndex = parseInt(option.value, 10);
            // Filter out months outside min/max range for the current year
            if (minDate && currentYear === minDate.getFullYear()) {
                const minMonth = minDate.getMonth();
                if (monthIndex < minMonth) {
                    return false;
                }
            }
            if (maxDate && currentYear === maxDate.getFullYear()) {
                const maxMonth = maxDate.getMonth();
                if (monthIndex > maxMonth) {
                    return false;
                }
            }
            return true;
        });
    }, [monthNames, currentYear, minDate, maxDate]);
    // Generate year options - filter based on minDate/maxDate
    const currentDate = new Date();
    const defaultYearRange = {
        start: currentDate.getFullYear() - 100,
        end: currentDate.getFullYear() + 10,
    };
    const actualYearRange = yearRange || defaultYearRange;
    const yearOptions = useMemo(() => {
        // Determine year range based on minDate/maxDate if provided
        let startYear = actualYearRange.start;
        let endYear = actualYearRange.end;
        if (minDate) {
            startYear = Math.max(startYear, minDate.getFullYear());
        }
        if (maxDate) {
            endYear = Math.max(endYear, maxDate.getFullYear());
        }
        const options = [];
        for (let year = endYear; year >= startYear; year--) {
            options.push({
                id: year,
                value: year.toString(),
                label: year.toString(),
            });
        }
        return options;
    }, [actualYearRange, minDate, maxDate]);
    // Current abbreviated month for display
    const currentAbbreviatedMonth = ABBREVIATED_MONTHS[currentMonth];
    // Dropdown change handlers
    const handleMonthChange = useCallback((monthValue) => {
        const newMonth = parseInt(monthValue, 10);
        const newDate = new Date(currentYear, newMonth, 1);
        if (onViewDateChange) {
            onViewDateChange(newDate);
        }
        else {
            setInternalViewDate(newDate);
        }
    }, [currentYear, onViewDateChange]);
    const handleYearChange = useCallback((yearValue) => {
        const newYear = parseInt(yearValue, 10);
        const newDate = new Date(newYear, currentMonth, 1);
        if (onViewDateChange) {
            onViewDateChange(newDate);
        }
        else {
            setInternalViewDate(newDate);
        }
    }, [currentMonth, onViewDateChange]);
    // Generate calendar days
    const calendarDays = useMemo(() => generateCalendarDays(currentYear, currentMonth, selectedValue, mode, showToday ? new Date() : null, minDate, maxDate, customIsDateDisabled), [
        currentYear,
        currentMonth,
        selectedValue,
        mode,
        showToday,
        minDate,
        maxDate,
        customIsDateDisabled,
    ]);
    // Navigation handlers
    const navigateToPreviousMonth = useCallback(() => {
        if (onNavigatePrev === null)
            return;
        if (onNavigatePrev) {
            onNavigatePrev();
        }
        else {
            const newDate = new Date(viewDate.getFullYear(), viewDate.getMonth() - 1, 1);
            if (onViewDateChange) {
                onViewDateChange(newDate);
            }
            else {
                setInternalViewDate(newDate);
            }
        }
    }, [onNavigatePrev, viewDate, onViewDateChange]);
    const navigateToNextMonth = useCallback(() => {
        if (onNavigateNext === null)
            return;
        if (onNavigateNext) {
            onNavigateNext();
        }
        else {
            const newDate = new Date(viewDate.getFullYear(), viewDate.getMonth() + 1, 1);
            if (onViewDateChange) {
                onViewDateChange(newDate);
            }
            else {
                setInternalViewDate(newDate);
            }
        }
    }, [onNavigateNext, viewDate, onViewDateChange]);
    // Date selection handler
    const handleDateSelect = useCallback((date) => {
        if (isDateDisabled(date, minDate, maxDate, customIsDateDisabled)) {
            return;
        }
        // Auto-navigate to the selected date's month if it's different from current view
        if (date.getMonth() !== currentMonth ||
            date.getFullYear() !== currentYear) {
            const newDate = new Date(date.getFullYear(), date.getMonth(), 1);
            if (onViewDateChange) {
                onViewDateChange(newDate);
            }
            else {
                setInternalViewDate(newDate);
            }
        }
        if (mode === 'single') {
            // Single date selection
            const newValue = date;
            // Update internal state if uncontrolled
            if (value === undefined) {
                setInternalValue(newValue);
            }
            // Call onChange callback
            onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
        }
        else {
            // Range selection
            const currentRange = selectedValue && typeof selectedValue === 'object' && 'start' in selectedValue
                ? selectedValue
                : { start: null, end: null };
            if (rangeSelectionState === 'start' || (currentRange.start && currentRange.end)) {
                // Starting a new range selection
                const newRange = { start: date, end: null };
                if (value === undefined) {
                    setInternalValue(newRange);
                }
                setRangeSelectionState('end');
                onChange === null || onChange === void 0 ? void 0 : onChange(newRange);
            }
            else {
                // Completing the range
                const start = currentRange.start;
                const end = date;
                // Ensure start is before end
                const newRange = start <= end
                    ? { start, end }
                    : { start: end, end: start };
                if (value === undefined) {
                    setInternalValue(newRange);
                }
                setRangeSelectionState('start');
                onChange === null || onChange === void 0 ? void 0 : onChange(newRange);
            }
        }
    }, [minDate, maxDate, customIsDateDisabled, currentMonth, currentYear, onViewDateChange, mode, value, onChange, selectedValue, rangeSelectionState]);
    return (jsxs("div", { ref: ref, className: cn(calendarTheme.baseStyle, className), ...props, children: [jsxs("div", { className: calendarTheme.headerStyle, children: [onNavigatePrev !== null && (jsx(Button, { onClick: navigateToPreviousMonth, "aria-label": "Previous month", disabled: !canNavigateToPrev, iconOnly: true, variant: "link", color: "neutral", size: "sm", children: jsx(ChevronLeftIcon, { className: "size-4", strokeWidth: 2 }) })), onNavigatePrev === null && jsx("div", { className: "w-8" }), resolvedHeaderMode === 'static' ? (jsxs("h2", { className: calendarTheme.titleStyle, children: [getMonthName(viewDate, months), " ", currentYear] })) : (jsxs("div", { className: calendarTheme.headerDropdownsStyle, children: [(resolvedHeaderMode === 'dropdown' || (resolvedHeaderMode === 'mixed' && enableMonthDropdown)) && (jsx(Select, { value: currentMonth.toString(), onChange: handleMonthChange, options: monthOptions, displayValue: currentAbbreviatedMonth, size: "sm", className: "text-base font-medium" })), (resolvedHeaderMode === 'dropdown' || (resolvedHeaderMode === 'mixed' && enableYearDropdown)) && (jsx(Select, { value: currentYear.toString(), onChange: handleYearChange, options: yearOptions, size: "sm", className: "text-base font-medium" }))] })), onNavigateNext !== null && (jsx(Button, { onClick: navigateToNextMonth, "aria-label": "Next month", disabled: !canNavigateToNext, iconOnly: true, variant: "link", color: "neutral", size: "sm", children: jsx(ChevronRightIcon, { className: "size-4", strokeWidth: 2 }) })), onNavigateNext === null && jsx("div", { className: "w-8" })] }), jsx("div", { className: calendarTheme.weekdaysStyle, children: weekdays.map((day, index) => (jsx("div", { className: calendarTheme.weekdayStyle, children: day }, index))) }), jsx("div", { className: calendarTheme.daysGridStyle, children: calendarDays.map((day, index) => {
                    var _a;
                    const dayNumber = day.date.getDate();
                    const isInStrip = day.isRangeStart || day.isRangeEnd || day.isInRange;
                    // Determine range position for strip rounding
                    let rangePos;
                    if (isInStrip) {
                        const isRowStart = index % 7 === 0;
                        const isRowEnd = index % 7 === 6;
                        const nextDay = calendarDays[index + 1];
                        const prevDay = calendarDays[index - 1];
                        const nextVisible = nextDay && (nextDay.isCurrentMonth || showOtherMonthDays);
                        const prevVisible = prevDay && (prevDay.isCurrentMonth || showOtherMonthDays);
                        const nextInStrip = nextVisible && (nextDay.isInRange || nextDay.isRangeEnd);
                        const prevInStrip = prevVisible && (prevDay.isInRange || prevDay.isRangeStart);
                        const effectiveStart = day.isRangeStart || isRowStart || !prevInStrip;
                        const effectiveEnd = day.isRangeEnd || isRowEnd || !nextInStrip;
                        if (effectiveStart && effectiveEnd)
                            rangePos = "single";
                        else if (effectiveStart)
                            rangePos = "start";
                        else if (effectiveEnd)
                            rangePos = "end";
                        else
                            rangePos = "middle";
                    }
                    if (!day.isCurrentMonth && !showOtherMonthDays) {
                        return jsx("div", {}, day.date.toISOString());
                    }
                    return (jsx("div", { className: cn("flex items-center justify-center", isInStrip && calendarTheme.rangeCellStyle), "data-range-pos": rangePos, children: jsxs(motion.button, { type: "button", onClick: () => handleDateSelect(day.date), disabled: day.isDisabled, className: cn(calendarTheme.dayButtonStyle, (_a = calendarTheme.dayButtonSizes) === null || _a === void 0 ? void 0 : _a[size]), "data-selected": day.isSelected, "data-range-start": day.isRangeStart, "data-range-end": day.isRangeEnd, "data-in-range": day.isInRange, "data-today": day.isToday && !day.isSelected && !day.isRangeStart && !day.isRangeEnd, "data-disabled": day.isDisabled, "data-other-month": !day.isCurrentMonth, "aria-label": `Select ${day.date.toLocaleDateString()}`, variants: shouldReduceMotion ? {} : calendarAnimations.dayButton, whileHover: shouldReduceMotion ? undefined : (!day.isDisabled ? "hover" : undefined), whileTap: shouldReduceMotion ? undefined : (!day.isDisabled ? "tap" : undefined), children: [jsx("time", { dateTime: day.date.toISOString().split("T")[0], children: dayNumber }), day.isToday && (jsx(motion.span, { className: "absolute z-10 bottom-1 left-1/2 -translate-x-1/2 w-1 h-1 bg-[var(--color-primary)] rounded-full", variants: shouldReduceMotion ? {} : calendarAnimations.todayDot, initial: shouldReduceMotion ? undefined : "initial", animate: shouldReduceMotion ? undefined : "animate" }))] }) }, day.date.toISOString()));
                }) })] }));
});
Calendar.displayName = "Calendar";

export { Calendar };
