/*
 * 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, __awaiter, __extends, __generator, __rest } from "tslib";
import classNames from "classnames";
import { addDays, format } from "date-fns";
import * as React from "react";
import { Boundary, DISPLAYNAME_PREFIX, Divider } from "@blueprintjs/core";
import { DatePickerShortcutMenu, DatePickerUtils, DateRangeSelectionStrategy, DateUtils, Errors, MonthAndYear, TimePicker, TimePrecision, } from "@blueprintjs/datetime";
import { Classes, dayPickerClassNameOverrides } from "../../classes";
import { combineModifiers, HOVERED_RANGE_MODIFIER } from "../../common/dayPickerModifiers";
import { DatePicker3Provider } from "../date-picker3/datePicker3Context";
import { DateFnsLocalizedComponent } from "../dateFnsLocalizedComponent";
import { ContiguousDayRangePicker } from "./contiguousDayRangePicker";
import { NonContiguousDayRangePicker } from "./nonContiguousDayRangePicker";
var NULL_RANGE = [null, null];
/**
 * Date range picker (v3) component.
 *
 * @see https://blueprintjs.com/docs/#datetime2/date-range-picker3
 */
var DateRangePicker3 = /** @class */ (function (_super) {
    __extends(DateRangePicker3, _super);
    function DateRangePicker3(props) {
        var _a, _b;
        var _this = _super.call(this, props) || this;
        // these will get merged with the user's own
        _this.modifiers = (_a = {},
            _a[HOVERED_RANGE_MODIFIER] = function (day) {
                var _a = _this.state, hoverValue = _a.hoverValue, _b = _a.value, selectedStart = _b[0], selectedEnd = _b[1];
                if (selectedStart == null && selectedEnd == null) {
                    return false;
                }
                if (hoverValue == null || hoverValue[0] == null || hoverValue[1] == null) {
                    return false;
                }
                return DateUtils.isDayInRange(day, hoverValue, true);
            },
            _a["".concat(HOVERED_RANGE_MODIFIER, "-start")] = function (day) {
                var hoverValue = _this.state.hoverValue;
                if (hoverValue == null || hoverValue[0] == null) {
                    return false;
                }
                return DateUtils.isSameDay(hoverValue[0], day);
            },
            _a["".concat(HOVERED_RANGE_MODIFIER, "-end")] = function (day) {
                var hoverValue = _this.state.hoverValue;
                if (hoverValue == null || hoverValue[1] == null) {
                    return false;
                }
                return DateUtils.isSameDay(hoverValue[1], day);
            },
            _a);
        _this.modifiersClassNames = (_b = {},
            _b[HOVERED_RANGE_MODIFIER] = Classes.DATERANGEPICKER3_HOVERED_RANGE,
            _b["".concat(HOVERED_RANGE_MODIFIER, "-start")] = Classes.DATERANGEPICKER3_HOVERED_RANGE_START,
            _b["".concat(HOVERED_RANGE_MODIFIER, "-end")] = Classes.DATERANGEPICKER3_HOVERED_RANGE_END,
            _b);
        _this.initialMonthAndYear = MonthAndYear.fromDate(new Date());
        _this.handleTimeChange = function (newTime, dateIndex) {
            var _a, _b, _c, _d;
            (_b = (_a = _this.props.timePickerProps) === null || _a === void 0 ? void 0 : _a.onChange) === null || _b === void 0 ? void 0 : _b.call(_a, newTime);
            var _e = _this.state, value = _e.value, time = _e.time;
            var newValue = DateUtils.getDateTime(value[dateIndex] != null ? DateUtils.clone(value[dateIndex]) : _this.getDefaultDate(dateIndex), newTime);
            var newDateRange = [value[0], value[1]];
            newDateRange[dateIndex] = newValue;
            var newTimeRange = [time[0], time[1]];
            newTimeRange[dateIndex] = newTime;
            (_d = (_c = _this.props).onChange) === null || _d === void 0 ? void 0 : _d.call(_c, newDateRange);
            _this.setState({ time: newTimeRange, value: newDateRange });
        };
        // When a user sets the time value before choosing a date, we need to pick a date for them
        // The default depends on the value of the other date since there's an invariant
        // that the left/0 date is always less than the right/1 date
        _this.getDefaultDate = function (dateIndex) {
            var value = _this.state.value;
            var otherIndex = dateIndex === 0 ? 1 : 0;
            var otherDate = value[otherIndex];
            if (otherDate == null) {
                return new Date();
            }
            var allowSingleDayRange = _this.props.allowSingleDayRange;
            if (!allowSingleDayRange) {
                var dateDiff = dateIndex === 0 ? -1 : 1;
                return addDays(otherDate, dateDiff);
            }
            return otherDate;
        };
        _this.handleTimeChangeLeftCalendar = function (time) {
            _this.handleTimeChange(time, 0);
        };
        _this.handleTimeChangeRightCalendar = function (time) {
            _this.handleTimeChange(time, 1);
        };
        /**
         * Custom formatter to render weekday names in the calendar header. The default formatter generally works fine,
         * but it was returning CAPITALIZED strings for some reason, while we prefer Title Case.
         */
        _this.formatWeekdayName = function (date) { return format(date, "EEEEEE", { locale: _this.state.locale }); };
        _this.handleDayMouseEnter = function (day, activeModifiers, e) {
            var _a, _b, _c, _d;
            (_b = (_a = _this.props.dayPickerProps) === null || _a === void 0 ? void 0 : _a.onDayMouseEnter) === null || _b === void 0 ? void 0 : _b.call(_a, day, activeModifiers, e);
            if (activeModifiers.disabled) {
                return;
            }
            var _e = DateRangeSelectionStrategy.getNextState(_this.state.value, day, _this.props.allowSingleDayRange, _this.props.boundaryToModify), dateRange = _e.dateRange, boundary = _e.boundary;
            _this.setState({ hoverValue: dateRange });
            (_d = (_c = _this.props).onHoverChange) === null || _d === void 0 ? void 0 : _d.call(_c, dateRange, day, boundary);
        };
        _this.handleDayMouseLeave = function (day, activeModifiers, e) {
            var _a, _b, _c, _d;
            (_b = (_a = _this.props.dayPickerProps) === null || _a === void 0 ? void 0 : _a.onDayMouseLeave) === null || _b === void 0 ? void 0 : _b.call(_a, day, activeModifiers, e);
            if (activeModifiers.disabled) {
                return;
            }
            _this.setState({ hoverValue: undefined });
            (_d = (_c = _this.props).onHoverChange) === null || _d === void 0 ? void 0 : _d.call(_c, undefined, day, undefined);
        };
        _this.handleDayRangeSelect = function (nextValue, selectedDay, boundary) {
            var _a, _b;
            // update the hovered date range after click to show the newly selected
            // state, at leasts until the mouse moves again
            _this.setState({ hoverValue: nextValue });
            (_b = (_a = _this.props).onHoverChange) === null || _b === void 0 ? void 0 : _b.call(_a, nextValue, selectedDay, boundary);
            _this.updateSelectedRange(nextValue);
        };
        _this.handleShortcutClick = function (shortcut, selectedShortcutIndex) {
            var _a, _b;
            var dateRange = shortcut.dateRange, includeTime = shortcut.includeTime;
            if (includeTime) {
                _this.updateSelectedRange(dateRange, [dateRange[0], dateRange[1]]);
            }
            else {
                _this.updateSelectedRange(dateRange);
            }
            if (_this.props.selectedShortcutIndex === undefined) {
                // uncontrolled shorcut selection
                _this.setState({ selectedShortcutIndex: selectedShortcutIndex });
            }
            (_b = (_a = _this.props).onShortcutChange) === null || _b === void 0 ? void 0 : _b.call(_a, shortcut, selectedShortcutIndex);
        };
        _this.updateSelectedRange = function (selectedRange, selectedTimeRange) {
            var _a, _b;
            if (selectedTimeRange === void 0) { selectedTimeRange = _this.state.time; }
            selectedRange[0] = DateUtils.getDateTime(selectedRange[0], selectedTimeRange[0]);
            selectedRange[1] = DateUtils.getDateTime(selectedRange[1], selectedTimeRange[1]);
            if (_this.props.value == null) {
                // uncontrolled range selection
                _this.setState({ time: selectedTimeRange, value: selectedRange });
            }
            (_b = (_a = _this.props).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, selectedRange);
        };
        var value = getInitialValue(props);
        var time = value;
        _this.initialMonthAndYear = MonthAndYear.fromDate(getInitialMonth(props, value));
        _this.state = {
            hoverValue: NULL_RANGE,
            locale: undefined,
            selectedShortcutIndex: _this.props.selectedShortcutIndex !== undefined ? _this.props.selectedShortcutIndex : -1,
            time: time,
            value: value,
        };
        return _this;
    }
    DateRangePicker3.prototype.render = function () {
        var _a;
        var _b = this.props, className = _b.className, contiguousCalendarMonths = _b.contiguousCalendarMonths, footerElement = _b.footerElement;
        var isSingleMonthOnly = getIsSingleMonthOnly(this.props);
        var classes = classNames(Classes.DATEPICKER, Classes.DATERANGEPICKER, className, (_a = {},
            _a[Classes.DATEPICKER3_HIGHLIGHT_CURRENT_DAY] = this.props.highlightCurrentDay,
            _a[Classes.DATERANGEPICKER_CONTIGUOUS] = contiguousCalendarMonths,
            _a[Classes.DATERANGEPICKER_SINGLE_MONTH] = isSingleMonthOnly,
            _a[Classes.DATERANGEPICKER3_REVERSE_MONTH_AND_YEAR] = this.props.reverseMonthAndYearMenus,
            _a));
        // use the left DayPicker when we only need one
        return (React.createElement("div", { className: classes },
            this.maybeRenderShortcuts(),
            React.createElement("div", { className: Classes.DATEPICKER_CONTENT },
                React.createElement(DatePicker3Provider, __assign({}, this.props, this.state),
                    contiguousCalendarMonths || isSingleMonthOnly
                        ? this.renderContiguousDayRangePicker(isSingleMonthOnly)
                        : this.renderNonContiguousDayRangePicker(),
                    this.maybeRenderTimePickers(isSingleMonthOnly),
                    footerElement))));
    };
    DateRangePicker3.prototype.componentDidMount = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, _super.prototype.componentDidMount.call(this)];
                    case 1:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    DateRangePicker3.prototype.componentDidUpdate = function (prevProps) {
        var _a;
        return __awaiter(this, void 0, void 0, function () {
            var isControlled;
            return __generator(this, function (_b) {
                _super.prototype.componentDidUpdate.call(this, prevProps);
                isControlled = prevProps.value !== undefined && this.props.value !== undefined;
                if (prevProps.contiguousCalendarMonths !== this.props.contiguousCalendarMonths) {
                    this.initialMonthAndYear = MonthAndYear.fromDate(getInitialMonth(this.props, getInitialValue(this.props)));
                }
                if (isControlled &&
                    (!DateUtils.areRangesEqual(prevProps.value, this.props.value) ||
                        prevProps.contiguousCalendarMonths !== this.props.contiguousCalendarMonths)) {
                    this.setState({ value: (_a = this.props.value) !== null && _a !== void 0 ? _a : NULL_RANGE });
                }
                if (this.props.selectedShortcutIndex !== prevProps.selectedShortcutIndex) {
                    this.setState({ selectedShortcutIndex: this.props.selectedShortcutIndex });
                }
                return [2 /*return*/];
            });
        });
    };
    DateRangePicker3.prototype.validateProps = function (props) {
        var defaultValue = props.defaultValue, initialMonth = props.initialMonth, maxDate = props.maxDate, minDate = props.minDate, boundaryToModify = props.boundaryToModify, value = props.value;
        var dateRange = [minDate, maxDate];
        if (defaultValue != null && !DateUtils.isDayRangeInRange(defaultValue, dateRange)) {
            console.error(Errors.DATERANGEPICKER_DEFAULT_VALUE_INVALID);
        }
        if (initialMonth != null && !DateUtils.isMonthInRange(initialMonth, dateRange)) {
            console.error(Errors.DATERANGEPICKER_INITIAL_MONTH_INVALID);
        }
        if (maxDate != null && minDate != null && maxDate < minDate && !DateUtils.isSameDay(maxDate, minDate)) {
            console.error(Errors.DATERANGEPICKER_MAX_DATE_INVALID);
        }
        if (value != null && !DateUtils.isDayRangeInRange(value, dateRange)) {
            console.error(Errors.DATERANGEPICKER_VALUE_INVALID);
        }
        if (boundaryToModify != null && boundaryToModify !== Boundary.START && boundaryToModify !== Boundary.END) {
            console.error(Errors.DATERANGEPICKER_PREFERRED_BOUNDARY_TO_MODIFY_INVALID);
        }
    };
    DateRangePicker3.prototype.maybeRenderShortcuts = function () {
        var shortcuts = this.props.shortcuts;
        if (shortcuts == null || shortcuts === false) {
            return null;
        }
        var selectedShortcutIndex = this.state.selectedShortcutIndex;
        var _a = this.props, allowSingleDayRange = _a.allowSingleDayRange, maxDate = _a.maxDate, minDate = _a.minDate, timePrecision = _a.timePrecision;
        return [
            React.createElement(DatePickerShortcutMenu, { key: "shortcuts", allowSingleDayRange: allowSingleDayRange, maxDate: maxDate, minDate: minDate, selectedShortcutIndex: selectedShortcutIndex, shortcuts: shortcuts, timePrecision: timePrecision, onShortcutClick: this.handleShortcutClick }),
            React.createElement(Divider, { key: "div" }),
        ];
    };
    DateRangePicker3.prototype.maybeRenderTimePickers = function (isShowingOneMonth) {
        var _a;
        // timePrecision may be set as a root prop or as a property inside timePickerProps, so we need to check both
        var _b = this.props, timePickerProps = _b.timePickerProps, _c = _b.timePrecision, timePrecision = _c === void 0 ? timePickerProps === null || timePickerProps === void 0 ? void 0 : timePickerProps.precision : _c;
        if (timePrecision == null && timePickerProps === DateRangePicker3.defaultProps.timePickerProps) {
            return null;
        }
        var isLongTimePicker = (timePickerProps === null || timePickerProps === void 0 ? void 0 : timePickerProps.useAmPm) ||
            timePrecision === TimePrecision.SECOND ||
            timePrecision === TimePrecision.MILLISECOND;
        return (React.createElement("div", { className: classNames(Classes.DATERANGEPICKER_TIMEPICKERS, (_a = {},
                _a[Classes.DATERANGEPICKER3_TIMEPICKERS_STACKED] = isShowingOneMonth && isLongTimePicker,
                _a)) },
            React.createElement(TimePicker, __assign({ precision: timePrecision }, timePickerProps, { onChange: this.handleTimeChangeLeftCalendar, value: this.state.time[0] })),
            React.createElement(TimePicker, __assign({ precision: timePrecision }, timePickerProps, { onChange: this.handleTimeChangeRightCalendar, value: this.state.time[1] }))));
    };
    /**
     * Render a standard day range picker where props.contiguousCalendarMonths is expected to be `true`.
     */
    DateRangePicker3.prototype.renderContiguousDayRangePicker = function (singleMonthOnly) {
        var _a = this.props, dayPickerProps = _a.dayPickerProps, props = __rest(_a, ["dayPickerProps"]);
        return (React.createElement(ContiguousDayRangePicker, __assign({}, props, { contiguousCalendarMonths: true, dayPickerEventHandlers: {
                onDayMouseEnter: this.handleDayMouseEnter,
                onDayMouseLeave: this.handleDayMouseLeave,
            }, dayPickerProps: this.resolvedDayPickerProps, initialMonthAndYear: this.initialMonthAndYear, locale: this.state.locale, onRangeSelect: this.handleDayRangeSelect, singleMonthOnly: singleMonthOnly, value: this.state.value })));
    };
    /**
     * react-day-picker doesn't have built-in support for non-contiguous calendar months in its range picker,
     * so we have to implement this ourselves.
     */
    DateRangePicker3.prototype.renderNonContiguousDayRangePicker = function () {
        var _a = this.props, dayPickerProps = _a.dayPickerProps, props = __rest(_a, ["dayPickerProps"]);
        return (React.createElement(NonContiguousDayRangePicker, __assign({}, props, { dayPickerProps: this.resolvedDayPickerProps, dayPickerEventHandlers: {
                onDayMouseEnter: this.handleDayMouseEnter,
                onDayMouseLeave: this.handleDayMouseLeave,
            }, initialMonthAndYear: this.initialMonthAndYear, locale: this.state.locale, onRangeSelect: this.handleDayRangeSelect, value: this.state.value })));
    };
    Object.defineProperty(DateRangePicker3.prototype, "resolvedDayPickerProps", {
        get: function () {
            var _a = this.props.dayPickerProps, dayPickerProps = _a === void 0 ? {} : _a;
            return __assign(__assign({}, dayPickerProps), { classNames: __assign(__assign({}, dayPickerClassNameOverrides), dayPickerProps.classNames), formatters: __assign({ formatWeekdayName: this.formatWeekdayName }, dayPickerProps.formatters), modifiers: combineModifiers(this.modifiers, dayPickerProps.modifiers), modifiersClassNames: __assign(__assign({}, this.modifiersClassNames), dayPickerProps.modifiersClassNames) });
        },
        enumerable: false,
        configurable: true
    });
    DateRangePicker3.defaultProps = {
        allowSingleDayRange: false,
        contiguousCalendarMonths: true,
        dayPickerProps: {},
        locale: "en-US",
        maxDate: DatePickerUtils.getDefaultMaxDate(),
        minDate: DatePickerUtils.getDefaultMinDate(),
        reverseMonthAndYearMenus: false,
        shortcuts: true,
        singleMonthOnly: false,
        timePickerProps: {},
    };
    DateRangePicker3.displayName = "".concat(DISPLAYNAME_PREFIX, ".DateRangePicker3");
    return DateRangePicker3;
}(DateFnsLocalizedComponent));
export { DateRangePicker3 };
function getIsSingleMonthOnly(props) {
    return props.singleMonthOnly || DateUtils.isSameMonth(props.minDate, props.maxDate);
}
function getInitialValue(props) {
    if (props.value != null) {
        return props.value;
    }
    if (props.defaultValue != null) {
        return props.defaultValue;
    }
    return NULL_RANGE;
}
function getInitialMonth(props, value) {
    var today = new Date();
    var isSingleMonthOnly = getIsSingleMonthOnly(props);
    if (props.initialMonth != null) {
        if (!isSingleMonthOnly && DateUtils.isSameMonth(props.initialMonth, props.maxDate)) {
            // special case: if initial month is same as maxDate month, display it on the right calendar
            return DateUtils.getDatePreviousMonth(props.initialMonth);
        }
        return props.initialMonth;
    }
    else if (value[0] != null) {
        if (!isSingleMonthOnly && DateUtils.isSameMonth(value[0], props.maxDate)) {
            // special case: if start of range is selected and that date is in the maxDate month, display it on the right calendar
            return DateUtils.getDatePreviousMonth(value[0]);
        }
        return DateUtils.clone(value[0]);
    }
    else if (value[1] != null) {
        var month = DateUtils.clone(value[1]);
        if (!DateUtils.isSameMonth(month, props.minDate)) {
            month.setMonth(month.getMonth() - 1);
        }
        return month;
    }
    else if (DateUtils.isDayInRange(today, [props.minDate, props.maxDate])) {
        if (!isSingleMonthOnly && DateUtils.isSameMonth(today, props.maxDate)) {
            // special case: if today is in the maxDate month, display it on the right calendar
            today.setMonth(today.getMonth() - 1);
        }
        return today;
    }
    else {
        var betweenDate = DateUtils.getDateBetween([props.minDate, props.maxDate]);
        if (!isSingleMonthOnly && DateUtils.isSameMonth(betweenDate, props.maxDate)) {
            // special case: if betweenDate is in the maxDate month, display it on the right calendar
            betweenDate.setMonth(betweenDate.getMonth() - 1);
        }
        return betweenDate;
    }
}
//# sourceMappingURL=dateRangePicker3.js.map