/*
 * Copyright 2023 Palantir Technologies, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { __assign, __rest } from "tslib";
import classNames from "classnames";
import * as React from "react";
import { DISPLAYNAME_PREFIX, InputGroup, Intent, mergeRefs, Popover, Tag, Utils, } from "@blueprintjs/core";
import { DatePickerUtils, DateUtils, Errors, TimezoneNameUtils, TimezoneSelect, TimezoneUtils, } from "@blueprintjs/datetime";
import { Classes } from "../../classes";
import { getDefaultDateFnsFormat } from "../../common/dateFnsFormatUtils";
import { useDateFnsLocale } from "../../common/dateFnsLocaleUtils";
import { DatePicker3 } from "../date-picker3/datePicker3";
import { useDateFormatter } from "./useDateFormatter";
import { useDateParser } from "./useDateParser";
var timezoneSelectButtonProps = {
    fill: false,
    minimal: true,
    outlined: true,
};
export var DATEINPUT3_DEFAULT_PROPS = {
    closeOnSelection: true,
    disabled: false,
    invalidDateMessage: "Invalid date",
    locale: "en-US",
    maxDate: DatePickerUtils.getDefaultMaxDate(),
    minDate: DatePickerUtils.getDefaultMinDate(),
    outOfRangeMessage: "Out of range",
    reverseMonthAndYearMenus: false,
};
/**
 * Date input (v3) component.
 *
 * @see https://blueprintjs.com/docs/#datetime2/date-input3
 */
export var DateInput3 = React.memo(function DateInput3(props) {
    var _a = props, closeOnSelection = _a.closeOnSelection, dateFnsFormat = _a.dateFnsFormat, dateFnsLocaleLoader = _a.dateFnsLocaleLoader, defaultTimezone = _a.defaultTimezone, defaultValue = _a.defaultValue, disabled = _a.disabled, disableTimezoneSelect = _a.disableTimezoneSelect, fill = _a.fill, _b = _a.inputProps, inputProps = _b === void 0 ? {} : _b, invalidDateMessage = _a.invalidDateMessage, localeOrCode = _a.locale, maxDate = _a.maxDate, minDate = _a.minDate, onChange = _a.onChange, onError = _a.onError, onTimezoneChange = _a.onTimezoneChange, outOfRangeMessage = _a.outOfRangeMessage, _c = _a.popoverProps, popoverProps = _c === void 0 ? {} : _c, popoverRef = _a.popoverRef, rightElement = _a.rightElement, showTimezoneSelect = _a.showTimezoneSelect, timePrecision = _a.timePrecision, controlledTimezone = _a.timezone, value = _a.value, datePickerProps = __rest(_a, ["closeOnSelection", "dateFnsFormat", "dateFnsLocaleLoader", "defaultTimezone", "defaultValue", "disabled", "disableTimezoneSelect", "fill", "inputProps", "invalidDateMessage", "locale", "maxDate", "minDate", "onChange", "onError", "onTimezoneChange", "outOfRangeMessage", "popoverProps", "popoverRef", "rightElement", "showTimezoneSelect", "timePrecision", "timezone", "value"]);
    var locale = useDateFnsLocale(localeOrCode, dateFnsLocaleLoader);
    var placeholder = getPlaceholder(props);
    var formatDateString = useDateFormatter(props, locale);
    var parseDateString = useDateParser(props, locale);
    // Refs
    // ------------------------------------------------------------------------
    var inputRef = React.useRef(null);
    var popoverContentRef = React.useRef(null);
    var popoverId = Utils.uniqueId("date-picker");
    // State
    // ------------------------------------------------------------------------
    var _d = React.useState(false), isOpen = _d[0], setIsOpen = _d[1];
    var _e = React.useState(getInitialTimezoneValue(props)), timezoneValue = _e[0], setTimezoneValue = _e[1];
    var valueFromProps = React.useMemo(function () { return TimezoneUtils.getDateObjectFromIsoString(value, timezoneValue); }, [timezoneValue, value]);
    var isControlled = valueFromProps !== undefined;
    var defaultValueFromProps = React.useMemo(function () { return TimezoneUtils.getDateObjectFromIsoString(defaultValue, timezoneValue); }, [defaultValue, timezoneValue]);
    var _f = React.useState(isControlled ? valueFromProps : defaultValueFromProps), valueAsDate = _f[0], setValue = _f[1];
    var _g = React.useState(undefined), selectedShortcutIndex = _g[0], setSelectedShortcutIndex = _g[1];
    var _h = React.useState(false), isInputFocused = _h[0], setIsInputFocused = _h[1];
    // rendered as the text input's value
    var formattedDateString = React.useMemo(function () { return (valueAsDate === null ? undefined : formatDateString(valueAsDate)); }, [valueAsDate, formatDateString]);
    var _j = React.useState(formattedDateString !== null && formattedDateString !== void 0 ? formattedDateString : undefined), inputValue = _j[0], setInputValue = _j[1];
    var isErrorState = valueAsDate != null &&
        (!DateUtils.isDateValid(valueAsDate) || !DateUtils.isDayInRange(valueAsDate, [minDate, maxDate]));
    // Effects
    // ------------------------------------------------------------------------
    React.useEffect(function () {
        if (isControlled) {
            setValue(valueFromProps);
        }
    }, [isControlled, valueFromProps]);
    React.useEffect(function () {
        // uncontrolled mode, updating initial timezone value
        if (defaultTimezone !== undefined && TimezoneNameUtils.isValidTimezone(defaultTimezone)) {
            setTimezoneValue(defaultTimezone);
        }
    }, [defaultTimezone]);
    React.useEffect(function () {
        // controlled mode, updating timezone value
        if (controlledTimezone !== undefined && TimezoneNameUtils.isValidTimezone(controlledTimezone)) {
            setTimezoneValue(controlledTimezone);
        }
    }, [controlledTimezone]);
    React.useEffect(function () {
        if (isControlled && !isInputFocused) {
            setInputValue(formattedDateString);
        }
    }, [isControlled, isInputFocused, formattedDateString]);
    // Popover contents (date picker)
    // ------------------------------------------------------------------------
    var handlePopoverClose = React.useCallback(function (e) {
        var _a;
        (_a = popoverProps.onClose) === null || _a === void 0 ? void 0 : _a.call(popoverProps, e);
        setIsOpen(false);
    }, [popoverProps]);
    var handleDateChange = React.useCallback(function (newDate, isUserChange, didSubmitWithEnter) {
        if (didSubmitWithEnter === void 0) { didSubmitWithEnter = false; }
        var prevDate = valueAsDate;
        if (newDate === null) {
            if (!isControlled && !didSubmitWithEnter) {
                // user clicked on current day in the calendar, so we should clear the input when uncontrolled
                setInputValue("");
            }
            onChange === null || onChange === void 0 ? void 0 : onChange(null, isUserChange);
            return;
        }
        // this change handler was triggered by a change in month, day, or (if
        // enabled) time. for UX purposes, we want to close the popover only if
        // the user explicitly clicked a day within the current month.
        var newIsOpen = !isUserChange ||
            !closeOnSelection ||
            (prevDate != null &&
                (DateUtils.hasMonthChanged(prevDate, newDate) ||
                    (timePrecision !== undefined && DateUtils.hasTimeChanged(prevDate, newDate))));
        // if selecting a date via click or Tab, the input will already be
        // blurred by now, so sync isInputFocused to false. if selecting via
        // Enter, setting isInputFocused to false won't do anything by itself,
        // plus we want the field to retain focus anyway.
        // (note: spelling out the ternary explicitly reads more clearly.)
        var newIsInputFocused = didSubmitWithEnter ? true : false;
        if (isControlled) {
            setIsInputFocused(newIsInputFocused);
            setIsOpen(newIsOpen);
        }
        else {
            var newFormattedDateString = formatDateString(newDate);
            setIsInputFocused(newIsInputFocused);
            setIsOpen(newIsOpen);
            setValue(newDate);
            setInputValue(newFormattedDateString);
        }
        var newIsoDateString = TimezoneUtils.getIsoEquivalentWithUpdatedTimezone(newDate, timezoneValue, timePrecision);
        onChange === null || onChange === void 0 ? void 0 : onChange(newIsoDateString, isUserChange);
    }, [closeOnSelection, isControlled, formatDateString, onChange, timezoneValue, timePrecision, valueAsDate]);
    var dayPickerProps = __assign(__assign({}, props.dayPickerProps), { onDayKeyDown: function (day, modifiers, e) {
            var _a, _b;
            (_b = (_a = props.dayPickerProps) === null || _a === void 0 ? void 0 : _a.onDayKeyDown) === null || _b === void 0 ? void 0 : _b.call(_a, day, modifiers, e);
        }, onMonthChange: function (month) {
            var _a, _b;
            (_b = (_a = props.dayPickerProps) === null || _a === void 0 ? void 0 : _a.onMonthChange) === null || _b === void 0 ? void 0 : _b.call(_a, month);
        } });
    var handleShortcutChange = React.useCallback(function (_, index) {
        setSelectedShortcutIndex(index);
    }, []);
    var handleStartFocusBoundaryFocusIn = React.useCallback(function (e) {
        var _a, _b, _c;
        if ((_a = popoverContentRef.current) === null || _a === void 0 ? void 0 : _a.contains(getRelatedTargetWithFallback(e))) {
            // Not closing Popover to allow user to freely switch between manually entering a date
            // string in the input and selecting one via the Popover
            (_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.focus();
        }
        else {
            (_c = getKeyboardFocusableElements(popoverContentRef).shift()) === null || _c === void 0 ? void 0 : _c.focus();
        }
    }, []);
    var handleEndFocusBoundaryFocusIn = React.useCallback(function (e) {
        var _a, _b, _c;
        if ((_a = popoverContentRef.current) === null || _a === void 0 ? void 0 : _a.contains(getRelatedTargetWithFallback(e))) {
            (_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.focus();
            handlePopoverClose(e);
        }
        else {
            (_c = getKeyboardFocusableElements(popoverContentRef).pop()) === null || _c === void 0 ? void 0 : _c.focus();
        }
    }, [handlePopoverClose]);
    // React's onFocus prop listens to the focusin browser event under the hood, so it's safe to
    // provide it the focusIn event handlers instead of using a ref and manually adding the
    // event listeners ourselves.
    var popoverContent = (React.createElement("div", { ref: popoverContentRef, role: "dialog", "aria-label": "date picker", id: popoverId },
        React.createElement("div", { onFocus: handleStartFocusBoundaryFocusIn, tabIndex: 0 }),
        React.createElement(DatePicker3, __assign({}, datePickerProps, { dateFnsLocaleLoader: dateFnsLocaleLoader, dayPickerProps: dayPickerProps, locale: locale, maxDate: maxDate, minDate: minDate, onChange: handleDateChange, onShortcutChange: handleShortcutChange, selectedShortcutIndex: selectedShortcutIndex, timePrecision: timePrecision, timezone: timezoneValue, 
            // the rest of this component handles invalid dates gracefully (to show error messages),
            // but DatePicker does not, so we must take care to filter those out
            value: isErrorState ? null : valueAsDate })),
        React.createElement("div", { onFocus: handleEndFocusBoundaryFocusIn, tabIndex: 0 })));
    // Timezone select
    // ------------------------------------------------------------------------
    // we need a date which is guaranteed to be non-null here; if necessary,
    // we use today's date and shift it to the desired/current timezone
    var tzSelectDate = React.useMemo(function () {
        return valueAsDate != null && DateUtils.isDateValid(valueAsDate)
            ? valueAsDate
            : TimezoneUtils.convertLocalDateToTimezoneTime(new Date(), timezoneValue);
    }, [timezoneValue, valueAsDate]);
    var isTimezoneSelectHidden = timePrecision === undefined || showTimezoneSelect === false;
    var isTimezoneSelectDisabled = disabled || disableTimezoneSelect;
    var handleTimezoneChange = React.useCallback(function (newTimezone) {
        if (controlledTimezone === undefined) {
            // uncontrolled timezone
            setTimezoneValue(newTimezone);
        }
        onTimezoneChange === null || onTimezoneChange === void 0 ? void 0 : onTimezoneChange(newTimezone);
        if (valueAsDate != null) {
            var newDateString = TimezoneUtils.getIsoEquivalentWithUpdatedTimezone(valueAsDate, newTimezone, timePrecision);
            onChange === null || onChange === void 0 ? void 0 : onChange(newDateString, true);
        }
    }, [onChange, onTimezoneChange, valueAsDate, timePrecision, controlledTimezone]);
    var maybeTimezonePicker = React.useMemo(function () {
        return isTimezoneSelectHidden ? undefined : (React.createElement(TimezoneSelect, { buttonProps: timezoneSelectButtonProps, className: Classes.DATE_INPUT_TIMEZONE_SELECT, date: tzSelectDate, disabled: isTimezoneSelectDisabled, onChange: handleTimezoneChange, value: timezoneValue },
            React.createElement(Tag, { endIcon: isTimezoneSelectDisabled ? undefined : "caret-down", interactive: !isTimezoneSelectDisabled, minimal: true }, TimezoneNameUtils.getTimezoneShortName(timezoneValue, tzSelectDate))));
    }, [handleTimezoneChange, isTimezoneSelectDisabled, isTimezoneSelectHidden, timezoneValue, tzSelectDate]);
    // Text input
    // ------------------------------------------------------------------------
    var handleInputFocus = React.useCallback(function (e) {
        var _a;
        setIsInputFocused(true);
        setIsOpen(true);
        setInputValue(formattedDateString);
        (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onFocus) === null || _a === void 0 ? void 0 : _a.call(inputProps, e);
    }, [formattedDateString, inputProps]);
    var handleInputBlur = React.useCallback(function (e) {
        var _a;
        if (inputValue == null || valueAsDate == null) {
            setIsInputFocused(false);
            return;
        }
        var date = parseDateString(inputValue);
        if (inputValue.length > 0 &&
            inputValue !== formattedDateString &&
            (!DateUtils.isDateValid(date) || !DateUtils.isDayInRange(date, [minDate, maxDate]))) {
            if (isControlled) {
                setIsInputFocused(false);
            }
            else {
                setIsInputFocused(false);
                setValue(date);
                setInputValue(undefined);
            }
            if (date === null) {
                onChange === null || onChange === void 0 ? void 0 : onChange(null, true);
            }
            else {
                onError === null || onError === void 0 ? void 0 : onError(date);
            }
        }
        else {
            if (inputValue.length === 0) {
                setIsInputFocused(false);
                setValue(null);
                setInputValue(undefined);
            }
            else {
                setIsInputFocused(false);
            }
        }
        (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onBlur) === null || _a === void 0 ? void 0 : _a.call(inputProps, e);
    }, [
        formattedDateString,
        inputProps,
        inputValue,
        isControlled,
        maxDate,
        minDate,
        onChange,
        onError,
        parseDateString,
        valueAsDate,
    ]);
    var handleInputChange = React.useCallback(function (e) {
        var _a;
        var valueString = e.target.value;
        var inputValueAsDate = parseDateString(valueString);
        if (DateUtils.isDateValid(inputValueAsDate) &&
            DateUtils.isDayInRange(inputValueAsDate, [minDate, maxDate])) {
            if (isControlled) {
                setInputValue(valueString);
            }
            else {
                setValue(inputValueAsDate);
                setInputValue(valueString);
            }
            var newIsoDateString = TimezoneUtils.getIsoEquivalentWithUpdatedTimezone(inputValueAsDate, timezoneValue, timePrecision);
            onChange === null || onChange === void 0 ? void 0 : onChange(newIsoDateString, true);
        }
        else {
            if (valueString.length === 0) {
                onChange === null || onChange === void 0 ? void 0 : onChange(null, true);
            }
            setValue(inputValueAsDate);
            setInputValue(valueString);
        }
        (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onChange) === null || _a === void 0 ? void 0 : _a.call(inputProps, e);
    }, [isControlled, minDate, maxDate, timezoneValue, timePrecision, parseDateString, onChange, inputProps]);
    var handleInputClick = React.useCallback(function (e) {
        var _a;
        // stop propagation to the Popover's internal handleTargetClick handler;
        // otherwise, the popover will flicker closed as soon as it opens.
        e.stopPropagation();
        (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onClick) === null || _a === void 0 ? void 0 : _a.call(inputProps, e);
    }, [inputProps]);
    var handleInputKeyDown = React.useCallback(function (e) {
        var _a, _b, _c;
        if (e.key === "Tab" && e.shiftKey) {
            // close popover on SHIFT+TAB key press
            handlePopoverClose(e);
        }
        else if (e.key === "Tab" && isOpen) {
            (_a = getKeyboardFocusableElements(popoverContentRef).shift()) === null || _a === void 0 ? void 0 : _a.focus();
            // necessary to prevent focusing the second focusable element
            e.preventDefault();
        }
        else if (e.key === "Escape") {
            setIsOpen(false);
            (_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.blur();
        }
        else if (e.key === "Enter" && inputValue != null) {
            var nextDate = parseDateString(inputValue);
            if (DateUtils.isDateValid(nextDate)) {
                handleDateChange(nextDate, true, true);
            }
        }
        (_c = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onKeyDown) === null || _c === void 0 ? void 0 : _c.call(inputProps, e);
    }, [handleDateChange, handlePopoverClose, inputProps, inputValue, isOpen, parseDateString]);
    // Main render
    // ------------------------------------------------------------------------
    var shouldShowErrorStyling = !isInputFocused || inputValue === outOfRangeMessage || inputValue === invalidDateMessage;
    // We use the renderTarget API to flatten the rendered DOM and make it easier to implement features like the "fill" prop.
    var renderTarget = React.useCallback(function (_a) {
        var _b;
        var targetIsOpen = _a.isOpen, ref = _a.ref, targetProps = __rest(_a, ["isOpen", "ref"]);
        return (React.createElement(InputGroup, __assign({ autoComplete: "off", className: classNames(targetProps.className, inputProps.className), intent: shouldShowErrorStyling && isErrorState ? Intent.DANGER : Intent.NONE, placeholder: placeholder, rightElement: React.createElement(React.Fragment, null,
                rightElement,
                maybeTimezonePicker), tagName: popoverProps.targetTagName, type: "text", role: "combobox" }, targetProps, inputProps, { "aria-controls": popoverId, "aria-expanded": targetIsOpen, disabled: disabled, fill: fill, inputRef: mergeRefs(ref, inputRef, inputProps === null || inputProps === void 0 ? void 0 : inputProps.inputRef), onBlur: handleInputBlur, onChange: handleInputChange, onClick: handleInputClick, onFocus: handleInputFocus, onKeyDown: handleInputKeyDown, value: (_b = (isInputFocused ? inputValue : formattedDateString)) !== null && _b !== void 0 ? _b : "" })));
    }, [
        disabled,
        fill,
        formattedDateString,
        handleInputBlur,
        handleInputChange,
        handleInputClick,
        handleInputFocus,
        handleInputKeyDown,
        inputProps,
        inputValue,
        isErrorState,
        isInputFocused,
        maybeTimezonePicker,
        placeholder,
        popoverId,
        popoverProps.targetTagName,
        rightElement,
        shouldShowErrorStyling,
    ]);
    // N.B. no need to set `fill` since that is unused with the `renderTarget` API
    return (React.createElement(Popover, __assign({ isOpen: isOpen && !disabled }, popoverProps, { autoFocus: false, className: classNames(Classes.DATE_INPUT, popoverProps.className, props.className), content: popoverContent, enforceFocus: false, onClose: handlePopoverClose, popoverClassName: classNames(Classes.DATE_INPUT_POPOVER, popoverProps.popoverClassName), ref: popoverRef, renderTarget: renderTarget })));
});
DateInput3.displayName = "".concat(DISPLAYNAME_PREFIX, ".DateInput3");
// TODO: Removing `defaultProps` here breaks tests. Investigate why.
// eslint-disable-next-line @typescript-eslint/no-deprecated
DateInput3.defaultProps = DATEINPUT3_DEFAULT_PROPS;
/** Gets the input `placeholder` value from props, using default values if undefined */
function getPlaceholder(props) {
    var _a;
    if (props.placeholder !== undefined || (props.formatDate !== undefined && props.parseDate !== undefined)) {
        return props.placeholder;
    }
    else {
        return (_a = props.dateFnsFormat) !== null && _a !== void 0 ? _a : getDefaultDateFnsFormat(props);
    }
}
function getInitialTimezoneValue(_a) {
    var defaultTimezone = _a.defaultTimezone, timezone = _a.timezone;
    if (timezone !== undefined) {
        // controlled mode
        if (TimezoneNameUtils.isValidTimezone(timezone)) {
            return timezone;
        }
        else {
            console.error(Errors.DATEINPUT_INVALID_TIMEZONE);
            return TimezoneUtils.UTC_TIME.ianaCode;
        }
    }
    else if (defaultTimezone !== undefined) {
        // uncontrolled mode with initial value
        if (TimezoneNameUtils.isValidTimezone(defaultTimezone)) {
            return defaultTimezone;
        }
        else {
            console.error(Errors.DATEINPUT_INVALID_DEFAULT_TIMEZONE);
            return TimezoneUtils.UTC_TIME.ianaCode;
        }
    }
    else {
        // uncontrolled mode
        return TimezoneUtils.getCurrentTimezone();
    }
}
function getRelatedTargetWithFallback(e) {
    var _a;
    return (_a = e.relatedTarget) !== null && _a !== void 0 ? _a : Utils.getActiveElement(e.currentTarget);
}
function getKeyboardFocusableElements(popoverContentRef) {
    if (popoverContentRef.current === null) {
        return [];
    }
    var elements = Utils.getFocusableElements(popoverContentRef.current);
    // Remove focus boundary div elements
    elements.pop();
    elements.shift();
    return elements;
}
//# sourceMappingURL=dateInput3.js.map