import { jsx, jsxs } from 'react/jsx-runtime';
import React__default, { createContext, useState, useRef, useMemo, useCallback, useEffect } from 'react';
import { FormLabel } from '../FormLabel/FormLabel.js';
import { timePickerTheme } from './TimePicker.theme.js';
import { cn } from '../../../utils/cn.js';
import { TimePickerTrigger } from './TimePickerTrigger.js';
import { TimePickerContent } from './TimePickerContent.js';
import { WheelColumn } from './WheelColumn.js';
import { formatTimeInput, isValidTimeInput } from '../Input/inputMasks.js';

const TimePickerContext = createContext(undefined);
/**
 * Utility functions for time handling
 */
// Parse time string into components - enhanced to handle various input formats
const parseTime = (timeString, format = "24h") => {
    if (!timeString || timeString.trim() === "")
        return null;
    // Clean the input string
    const cleanTime = timeString.trim().toUpperCase();
    // Handle various input formats
    let parsedHour = 0;
    let parsedMinute = 0;
    let parsedPeriod;
    // Try different parsing strategies
    // Strategy 1: Standard format with colons (14:30, 2:30 PM)
    const standardRegex = format === "12h"
        ? /^(\d{1,2}):(\d{2})\s*(AM|PM)?$/i
        : /^(\d{1,2}):(\d{2})$/;
    let match = cleanTime.match(standardRegex);
    if (match) {
        parsedHour = parseInt(match[1], 10);
        parsedMinute = parseInt(match[2], 10);
        parsedPeriod = match[3] ? match[3] : undefined;
    }
    else {
        // Strategy 2: Flexible format (230, 230p, 1430, etc.)
        const flexibleRegex = /^(\d{1,4})\s*(A|P|AM|PM)?$/i;
        match = cleanTime.match(flexibleRegex);
        if (match) {
            const digits = match[1];
            parsedPeriod = match[2]
                ? match[2].length === 1
                    ? `${match[2]}M`
                    : match[2]
                : undefined;
            if (digits.length <= 2) {
                // Just hours (2, 14)
                parsedHour = parseInt(digits, 10);
                parsedMinute = 0;
            }
            else if (digits.length === 3) {
                // Hours and minutes without leading zero (230 = 2:30)
                parsedHour = parseInt(digits.charAt(0), 10);
                parsedMinute = parseInt(digits.substring(1), 10);
            }
            else if (digits.length === 4) {
                // Hours and minutes (1430 = 14:30)
                parsedHour = parseInt(digits.substring(0, 2), 10);
                parsedMinute = parseInt(digits.substring(2), 10);
            }
        }
        else {
            return null; // Unable to parse
        }
    }
    // Convert 12-hour to 24-hour for internal consistency
    if (format === "12h" && parsedPeriod) {
        if (parsedPeriod === "AM" && parsedHour === 12) {
            parsedHour = 0;
        }
        else if (parsedPeriod === "PM" && parsedHour !== 12) {
            parsedHour += 12;
        }
    }
    // Validate parsed values
    if (parsedHour < 0 ||
        parsedHour > 23 ||
        parsedMinute < 0 ||
        parsedMinute > 59) {
        return null;
    }
    return {
        hour: parsedHour,
        minute: parsedMinute,
        period: format === "12h" ? (parsedHour >= 12 ? "PM" : "AM") : undefined,
    };
};
// Format time components into string
const formatTime = (time, format = "24h") => {
    let { hour, minute, period } = time;
    if (format === "12h") {
        // Convert to 12-hour format
        if (hour === 0) {
            hour = 12;
            period = "AM";
        }
        else if (hour === 12) {
            period = "PM";
        }
        else if (hour > 12) {
            hour = hour - 12;
            period = "PM";
        }
        else {
            period = "AM";
        }
        return `${hour}:${minute.toString().padStart(2, "0")} ${period}`;
    }
    else {
        // 24-hour format
        return `${hour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
    }
};
// Validate time against constraints
const validateTime = (time, minTime, maxTime) => {
    const { hour, minute } = time;
    // Basic validation
    if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
        return { isValid: false, error: "Invalid time" };
    }
    // Convert to minutes for comparison
    const timeMinutes = hour * 60 + minute;
    if (minTime) {
        const minParsed = parseTime(minTime, "24h");
        if (minParsed) {
            const minMinutes = minParsed.hour * 60 + minParsed.minute;
            if (timeMinutes < minMinutes) {
                return { isValid: false, error: `Time must be after ${minTime}` };
            }
        }
    }
    if (maxTime) {
        const maxParsed = parseTime(maxTime, "24h");
        if (maxParsed) {
            const maxMinutes = maxParsed.hour * 60 + maxParsed.minute;
            if (timeMinutes > maxMinutes) {
                return { isValid: false, error: `Time must be before ${maxTime}` };
            }
        }
    }
    return { isValid: true };
};
// Generate time options for selectors
const generateHours = (format) => {
    if (format === "12h") {
        return Array.from({ length: 12 }, (_, i) => i + 1);
    }
    return Array.from({ length: 24 }, (_, i) => i);
};
const generateMinutes = (step) => {
    return Array.from({ length: 60 / step }, (_, i) => i * step);
};
// Convert 24h hour to 12h display hour for wheel
const getDisplayHour = (hour24, format) => {
    if (format === "12h") {
        if (hour24 === 0)
            return 12;
        if (hour24 > 12)
            return hour24 - 12;
        return hour24;
    }
    return hour24;
};
// Convert 12h display hour back to 24h for storage
const get24Hour = (displayHour, period) => {
    if (period === "AM") {
        return displayHour === 12 ? 0 : displayHour;
    }
    else {
        return displayHour === 12 ? 12 : displayHour + 12;
    }
};
/**
 * Main TimePicker Component
 *
 * Features:
 * - iOS-style scroll wheel picker
 * - Combobox-style masked input trigger with direct keyboard entry
 * - Live bidirectional sync between input and wheels
 * - 12h and 24h format support with AM/PM toggle
 * - Cancel/Save workflow for confirming selection
 * - Validation with min/max time constraints
 */
const TimePicker = React__default.forwardRef(({ value, defaultValue, onChange, format = "24h", step = 1, minTime, maxTime, disabled = false, placeholder = format === "12h"
    ? "HH:MM"
    : "HH:MM", size = "md", state = "default", clearable = true, label, helperText, iconStart, iconEnd, className, labelClassName, helperTextClassName, theme = {}, id, ...props }, ref) => {
    var _a;
    // Internal state
    const [isOpen, setIsOpen] = useState(false);
    const [internalValue, setInternalValue] = useState(defaultValue);
    const [validationError, setValidationError] = useState();
    // Pending state for wheel picker (before Save is clicked)
    const [pendingTime, setPendingTime] = useState(null);
    // Masked input value
    const [maskedValue, setMaskedValue] = useState("");
    // Track whether the input or wheels initiated the last change (prevent feedback loops)
    const syncSourceRef = useRef(null);
    // Refs
    const containerRef = useRef(null);
    const triggerRef = useRef(null);
    const inputRef = useRef(null);
    const dropdownRef = useRef(null);
    // Determine current value (controlled vs uncontrolled)
    const currentValue = value !== undefined ? value : internalValue;
    // Parse current time for display
    const currentTime = parseTime(currentValue, format) || {
        hour: format === "12h" ? 12 : 0,
        minute: 0,
        period: format === "12h" ? "AM" : undefined,
    };
    // Get display value for trigger
    const displayValue = currentValue ? formatTime(currentTime, format) : "";
    // Merge theme with defaults
    const mergedTheme = useMemo(() => ({ ...timePickerTheme, ...theme }), [theme]);
    // Generate unique ID
    const generatedId = React__default.useId();
    const inputId = id || generatedId;
    // Determine if component is in error state
    const isInvalid = state === "invalid" || Boolean(validationError);
    const finalState = isInvalid ? "invalid" : state;
    // Generate selector options
    const hourOptions = useMemo(() => generateHours(format), [format]);
    const minuteOptions = useMemo(() => generateMinutes(step), [step]);
    const periodOptions = ["AM", "PM"];
    // Get the time to show in wheel picker (pending or current)
    const wheelTime = pendingTime || currentTime;
    // ============================================
    // Input handlers
    // ============================================
    // Handle masked input change
    const handleInputChange = useCallback((rawValue) => {
        const formatKey = format === "12h" ? "12h" : "24h";
        const formatted = formatTimeInput(rawValue, formatKey);
        setMaskedValue(formatted);
        // If complete valid time, sync to wheels
        if (isValidTimeInput(formatted, formatKey)) {
            const match = formatted.match(/^(\d{2}):(\d{2})$/);
            if (match) {
                let hour = parseInt(match[1], 10);
                const minute = parseInt(match[2], 10);
                // For 12h, convert display hour to 24h using current period
                if (format === "12h") {
                    const currentPeriod = (pendingTime === null || pendingTime === void 0 ? void 0 : pendingTime.period) || currentTime.period || "AM";
                    hour = get24Hour(hour, currentPeriod);
                }
                syncSourceRef.current = "input";
                setPendingTime((prev) => ({
                    hour,
                    minute,
                    period: format === "12h" ? ((prev === null || prev === void 0 ? void 0 : prev.period) || currentTime.period || "AM") : undefined,
                }));
            }
        }
        // Open dropdown if not already open
        if (!isOpen) {
            setPendingTime((prev) => prev || { ...currentTime });
            setIsOpen(true);
        }
    }, [format, isOpen, pendingTime, currentTime]);
    // Handle input focus — open dropdown
    const handleInputFocus = useCallback(() => {
        if (disabled)
            return;
        if (!isOpen) {
            setPendingTime({ ...currentTime });
            setIsOpen(true);
            // Initialize maskedValue from current display
            if (currentValue) {
                const displayHour = getDisplayHour(currentTime.hour, format);
                const formatted = `${displayHour.toString().padStart(2, "0")}:${currentTime.minute.toString().padStart(2, "0")}`;
                setMaskedValue(formatted);
            }
        }
    }, [disabled, isOpen, currentTime, currentValue]);
    // Handle AM/PM period toggle
    const handlePeriodToggle = useCallback(() => {
        setPendingTime((prev) => {
            const current = prev || currentTime;
            const newPeriod = current.period === "AM" ? "PM" : "AM";
            const displayHour = getDisplayHour(current.hour, format);
            const hour24 = get24Hour(displayHour, newPeriod);
            syncSourceRef.current = "wheel";
            return { ...current, hour: hour24, period: newPeriod };
        });
    }, [currentTime, format]);
    // ============================================
    // Wheel handlers
    // ============================================
    // Handle hour change from wheel
    const handleHourChange = useCallback((newHour) => {
        const hour = typeof newHour === "string" ? parseInt(newHour) : newHour;
        syncSourceRef.current = "wheel";
        setPendingTime((prev) => {
            const current = prev || currentTime;
            if (format === "12h") {
                // Convert back to 24h
                const hour24 = get24Hour(hour, current.period || "AM");
                return { ...current, hour: hour24 };
            }
            return { ...current, hour };
        });
    }, [currentTime, format]);
    // Handle minute change from wheel
    const handleMinuteChange = useCallback((newMinute) => {
        const minute = typeof newMinute === "string" ? parseInt(newMinute) : newMinute;
        syncSourceRef.current = "wheel";
        setPendingTime((prev) => {
            const current = prev || currentTime;
            return { ...current, minute };
        });
    }, [currentTime]);
    // Handle period change from wheel (12h format only)
    const handlePeriodChange = useCallback((newPeriod) => {
        const period = newPeriod;
        syncSourceRef.current = "wheel";
        setPendingTime((prev) => {
            const current = prev || currentTime;
            // Recalculate hour based on new period
            const displayHour = getDisplayHour(current.hour, format);
            const hour24 = get24Hour(displayHour, period);
            return { ...current, hour: hour24, period };
        });
    }, [currentTime, format]);
    // ============================================
    // Save / Cancel / Clear
    // ============================================
    // Handle Save button - commit pending time
    const handleSave = useCallback(() => {
        if (pendingTime) {
            // Validate time
            const validation = validateTime(pendingTime, minTime, maxTime);
            if (!validation.isValid) {
                setValidationError(validation.error);
                return;
            }
            else {
                setValidationError(undefined);
            }
            const formattedTime = formatTime(pendingTime, format);
            // Update internal state if uncontrolled
            if (value === undefined) {
                setInternalValue(formattedTime);
            }
            // Call onChange callback
            onChange === null || onChange === void 0 ? void 0 : onChange(formattedTime);
        }
        setIsOpen(false);
        setPendingTime(null);
        setMaskedValue("");
    }, [pendingTime, format, minTime, maxTime, value, onChange]);
    // Handle Cancel button - discard pending time
    const handleCancel = useCallback(() => {
        setIsOpen(false);
        setPendingTime(null);
        setMaskedValue("");
    }, []);
    // Handle input key down
    const handleInputKeyDown = useCallback((e) => {
        var _a;
        if (e.key === "Enter") {
            e.preventDefault();
            handleSave();
        }
        else if (e.key === "Escape") {
            e.preventDefault();
            handleCancel();
        }
        else if (e.key === "Tab") {
            handleCancel();
        }
        else if (e.key === "Backspace") {
            // Handle backspace across the colon: if cursor is right after ":",
            // delete both the colon and the preceding digit
            const input = e.currentTarget;
            const cursorPos = (_a = input.selectionStart) !== null && _a !== void 0 ? _a : 0;
            if (cursorPos === 3 && maskedValue[2] === ":") {
                e.preventDefault();
                // Remove the digit before colon and the colon itself → keep just first char
                const newValue = maskedValue.slice(0, 1);
                setMaskedValue(newValue);
            }
        }
    }, [maskedValue, handleSave, handleCancel]);
    // Portal-aware click-outside detection (matches Select/Combobox pattern)
    useEffect(() => {
        if (!isOpen)
            return;
        const handleClickOutside = (event) => {
            if (dropdownRef.current &&
                !dropdownRef.current.contains(event.target) &&
                containerRef.current &&
                !containerRef.current.contains(event.target)) {
                handleCancel();
            }
        };
        document.addEventListener("mousedown", handleClickOutside);
        return () => document.removeEventListener("mousedown", handleClickOutside);
    }, [isOpen, handleCancel]);
    // Handle clear button click
    const handleClear = () => {
        if (value === undefined) {
            setInternalValue(undefined);
        }
        onChange === null || onChange === void 0 ? void 0 : onChange("");
        setMaskedValue("");
        setValidationError(undefined);
    };
    // ============================================
    // Bidirectional sync: wheels → input
    // ============================================
    useEffect(() => {
        if (pendingTime && syncSourceRef.current === "wheel") {
            const displayHour = getDisplayHour(pendingTime.hour, format);
            const formatted = `${displayHour.toString().padStart(2, "0")}:${pendingTime.minute.toString().padStart(2, "0")}`;
            setMaskedValue(formatted);
        }
        // Reset sync source after processing
        syncSourceRef.current = null;
    }, [pendingTime, format]);
    // Context value
    const contextValue = useMemo(() => ({
        isOpen,
        setIsOpen,
        theme: mergedTheme,
        triggerRef,
        inputRef,
        hasValue: Boolean(displayValue),
        size,
        state: finalState,
    }), [isOpen, mergedTheme, displayValue, size, finalState]);
    // Current period for trigger display
    const currentPeriod = (pendingTime === null || pendingTime === void 0 ? void 0 : pendingTime.period) || currentTime.period || (format === "12h" ? "AM" : undefined);
    // Compute the value shown in the input:
    // - When open: show maskedValue (user is editing)
    // - When closed with a committed value: show formatted HH:MM from currentValue
    // - When closed without a value: show "" (placeholder visible)
    const closedInputDisplay = currentValue
        ? `${getDisplayHour(currentTime.hour, format).toString().padStart(2, "0")}:${currentTime.minute.toString().padStart(2, "0")}`
        : "";
    const inputDisplayValue = isOpen ? maskedValue : closedInputDisplay;
    return (jsx(TimePickerContext.Provider, { value: contextValue, children: jsxs("div", { ref: (el) => {
                containerRef.current = el;
                if (typeof ref === 'function') {
                    ref(el);
                }
                else if (ref) {
                    ref.current = el;
                }
            }, className: cn(mergedTheme.containerStyle, className), children: [label && (jsx(FormLabel, { htmlFor: inputId, className: labelClassName, state: finalState, required: props.required, children: label })), jsx(TimePickerTrigger, { id: inputId, disabled: disabled, isOpen: isOpen, state: finalState, size: size, clearable: clearable, onClear: handleClear, iconStart: iconStart, iconEnd: iconEnd, inputValue: inputDisplayValue, onInputChange: handleInputChange, onInputFocus: handleInputFocus, onInputKeyDown: handleInputKeyDown, format: format, period: currentPeriod, onPeriodToggle: handlePeriodToggle, placeholder: placeholder }), jsxs(TimePickerContent, { isOpen: isOpen, onCancel: handleCancel, onSave: handleSave, portal: true, placement: "bottom-start", offset: 4, dropdownRef: dropdownRef, children: [jsx(WheelColumn, { label: "Hour", options: hourOptions, value: getDisplayHour(wheelTime.hour, format), onChange: handleHourChange, theme: mergedTheme }), jsx(WheelColumn, { label: "Min", options: minuteOptions, value: wheelTime.minute, onChange: handleMinuteChange, theme: mergedTheme }), format === "12h" && (jsx(WheelColumn, { label: "", options: periodOptions, value: wheelTime.period || "AM", onChange: handlePeriodChange, theme: mergedTheme }))] }), (helperText || validationError) && (jsx("p", { className: cn(mergedTheme.helperTextStyle, (_a = mergedTheme.helperTextStates) === null || _a === void 0 ? void 0 : _a[finalState], helperTextClassName), children: validationError || helperText }))] }) }));
});
TimePicker.displayName = "TimePicker";

export { TimePicker, TimePickerContext };
