import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
// (C) 2020-2026 GoodData Corporation
import { PureComponent, createRef, } from "react";
import classNames from "classnames";
import { format, isSameDay, isValid, parse } from "date-fns";
import { de, enAU, enGB, enUS, es, fi, fr, frCA, id, it, ja, ko, nl, pl, pt, ptBR, ru, sl, th, tr, uk, vi, zhCN, } from "date-fns/locale";
import { debounce } from "lodash-es";
import { DayPicker } from "react-day-picker";
import { injectIntl } from "react-intl";
import { v4 as uuid } from "uuid";
import { IntlWrapper } from "@gooddata/sdk-ui";
import { DEFAULT_DATE_FORMAT } from "../constants/platform.js";
import { elementRegion } from "../utils/domUtilities.js";
import { isEnterKey } from "../utils/events.js";
import { getOptimalAlignment } from "../utils/overlay.js";
const DATEPICKER_OUTSIDE_DAY_SELECTOR = "rdp-outside";
const convertedLocales = {
    "en-US": enUS,
    "de-DE": de,
    "es-ES": es,
    "fr-FR": fr,
    "ja-JP": ja,
    "nl-NL": nl,
    "pt-BR": ptBR,
    "pt-PT": pt,
    "zh-Hans": zhCN,
    "ru-RU": ru,
    "it-IT": it,
    "es-419": es,
    "en-GB": enGB,
    "fr-CA": frCA,
    "zh-Hant": zhCN,
    "zh-HK": zhCN,
    "en-AU": enAU,
    "fi-FI": fi,
    "tr-TR": tr,
    "pl-PL": pl,
    "ko-KR": ko,
    "sl-SI": sl,
    "id-ID": id,
    "th-TH": th,
    "uk-UA": uk,
    "vi-VN": vi,
};
function formatDate(date, dateFormat) {
    return format(date, dateFormat);
}
function convertLocale(locale) {
    return convertedLocales[locale];
}
export function parseDate(str, dateFormat) {
    try {
        const parsedDate = parse(str, dateFormat, new Date());
        // parse only dates with 4-digit years. this mimics moment.js behavior - it parses only dates above 1900
        // this is to make sure that the picker input is not overwritten in the middle of writing the year with year "0002" when writing 2020.
        //
        // it's also necessary to parse only when the input string fully matches with the desired format
        // to make sure that the picker input is not overwritten in the middle of writing.
        // e.g, let's consider a case where dateFormat is "dd/MM/yyyy" and the DayPickerInput has already been filled with a valid string "13/09/2020",
        // then an user wants to change only the month "13/09/2020" -> "13/11/2020" by removing "09" and typing "11".
        // in such case the parsing should wait until the user completes typing "11" (otherwise if parsing is done right after the first "1" is typed,
        // the cursor automatically moves to the end of the string in the middle of writing, causing a bad experience for the user).
        if (isValid(parsedDate) &&
            parsedDate.getFullYear() >= 1000 &&
            str === formatDate(parsedDate, dateFormat)) {
            return parsedDate;
        }
        return undefined;
    }
    catch {
        return undefined;
    }
}
function convertWeekStart(weekStart) {
    switch (weekStart) {
        case "Sunday":
            return 0;
        case "Monday":
            return 1;
        default:
            throw new Error(`Unknown week start ${weekStart}`);
    }
}
export class WrappedDatePicker extends PureComponent {
    rootRef = null;
    datePickerContainerRef = createRef();
    inputRef = createRef();
    datePickerId = uuid();
    static defaultProps = {
        className: "",
        date: new Date(),
        placeholder: "",
        onChange: () => { },
        onBlur: () => { },
        resetOnInvalidValue: false,
        size: "",
        tabIndex: 0,
        alignPoints: [{ align: "bl tl" }, { align: "br tr" }, { align: "tl bl" }, { align: "tr br" }],
        onAlign: () => { },
        dateFormat: DEFAULT_DATE_FORMAT,
        weekStart: "Sunday",
    };
    constructor(props) {
        super(props);
        const { alignPoints, date, dateFormat } = props;
        this.state = {
            align: alignPoints?.[0]?.align ?? "bl tl",
            selectedDate: date,
            monthDate: date,
            inputValue: formatDate(date || new Date(), dateFormat ?? DEFAULT_DATE_FORMAT),
            isOpen: false,
        };
        this.handleDayChanged = this.handleDayChanged.bind(this);
        this.handleMonthChanged = this.handleMonthChanged.bind(this);
        this.handleInputChanged = this.handleInputChanged.bind(this);
        this.handleInputBlur = this.handleInputBlur.bind(this);
        this.alignDatePicker = this.alignDatePicker.bind(this);
        this.setComponentRef = this.setComponentRef.bind(this);
        this.handleWrapperClick = this.handleWrapperClick.bind(this);
        this.handleClickOutside = this.handleClickOutside.bind(this);
        this.onKeyDown = this.onKeyDown.bind(this);
        this.handleCustomDayClick = this.handleCustomDayClick.bind(this);
    }
    componentDidMount() {
        const { date, dateFormat } = this.props;
        this.setState({ selectedDate: this.updateDate(date || new Date()) });
        this.setState({ inputValue: formatDate(date || new Date(), dateFormat ?? DEFAULT_DATE_FORMAT) });
        window.addEventListener("resize", this.resizeHandler);
        document.addEventListener("mousedown", this.handleClickOutside);
    }
    UNSAFE_componentWillReceiveProps(nextProps) {
        const { props } = this;
        const propsDate = props.date?.getTime() ?? 0;
        const nextPropsDate = nextProps.date?.getTime() ?? 0;
        if (propsDate !== nextPropsDate) {
            const selectedDate = this.updateDate(nextProps.date || new Date());
            this.setState({ selectedDate });
            this.setState({ monthDate: selectedDate });
            this.setState({ inputValue: formatDate(selectedDate, props.dateFormat ?? DEFAULT_DATE_FORMAT) });
        }
    }
    componentWillUnmount() {
        window.removeEventListener("resize", this.resizeHandler);
        document.removeEventListener("mousedown", this.handleClickOutside);
    }
    handleClickOutside(event) {
        if (this.datePickerContainerRef.current &&
            !this.datePickerContainerRef.current.contains(event.target) &&
            this.inputRef.current &&
            !this.inputRef.current.contains(event.target)) {
            this.setState({ isOpen: false });
        }
    }
    componentDidUpdate(_prevProps, prevState) {
        if (this.state.isOpen && !prevState.isOpen) {
            this.alignDatePicker();
        }
    }
    setComponentRef(ref) {
        this.rootRef = ref;
    }
    getInputClasses() {
        return classNames("input-text", "small-12", this.props.size, `gd-datepicker-input-${this.datePickerId}`);
    }
    getComponentClasses() {
        return classNames("gd-datepicker", this.props.className, this.props.size, "gd-datepicker-input", this.state.isOpen ? "gd-datepicker-focused" : "");
    }
    getOverlayWrapperClasses() {
        const [inputAnchorPoint, pickerAnchorPoint] = this.state.align.split(" ");
        return classNames("gd-datepicker-picker", "gd-datepicker-OverlayWrapper", `gd-datepicker-OverlayWrapper-${inputAnchorPoint}-xx`, `gd-datepicker-OverlayWrapper-xx-${pickerAnchorPoint}`);
    }
    resizeHandler = debounce(() => this.alignDatePicker(), 100);
    updateDate(date) {
        return this.normalizeDate(date);
    }
    handleInputBlur(e) {
        this.props.onBlur?.(e.target.value);
    }
    handleInputChanged(e) {
        const { value } = e.target;
        const parsedDate = parseDate(value, this.props.dateFormat ?? DEFAULT_DATE_FORMAT);
        this.props.onValidateInput?.(value);
        this.setState({ inputValue: value });
        if (parsedDate) {
            this.setState({
                selectedDate: parsedDate,
                monthDate: parsedDate,
            }, () => {
                if (this.state.selectedDate) {
                    this.props.onChange?.(this.state.selectedDate);
                }
            });
        }
        else {
            if (this.props.resetOnInvalidValue) {
                this.setState({
                    selectedDate: this.state.selectedDate,
                    monthDate: this.state.selectedDate,
                });
                return;
            }
            this.setState({
                selectedDate: undefined,
                monthDate: undefined,
            }, () => {
                // Signal invalid state by passing null
                this.props.onChange?.(null);
            });
        }
    }
    handleDayChanged(newlySelectedDate) {
        if (!newlySelectedDate) {
            this.setState({ isOpen: false });
            return;
        }
        if (this.state.selectedDate && isSameDay(this.state.selectedDate, newlySelectedDate)) {
            this.setState({ isOpen: false });
            return;
        }
        this.inputRef.current?.focus();
        this.props.onValidateInput?.(formatDate(newlySelectedDate, this.props.dateFormat ?? DEFAULT_DATE_FORMAT));
        this.setState({
            selectedDate: newlySelectedDate,
            monthDate: newlySelectedDate,
            inputValue: formatDate(newlySelectedDate, this.props.dateFormat ?? DEFAULT_DATE_FORMAT),
            isOpen: false,
        }, () => {
            this.props.onChange?.(newlySelectedDate);
        });
    }
    handleMonthChanged(month) {
        this.inputRef.current?.focus();
        this.setState({ monthDate: month });
    }
    handleCustomDayClick = (day, _modifiers) => {
        // Handle all day clicks, including outside days
        this.handleDayChanged(day);
    };
    normalizeDate(date) {
        return new Date(date.getFullYear(), date.getMonth(), date.getDate());
    }
    alignDatePicker() {
        const { alignPoints } = this.props;
        const container = this.datePickerContainerRef.current?.parentElement;
        if (!alignPoints || !container || !this.rootRef) {
            return;
        }
        const optimalAlignment = getOptimalAlignment({
            targetRegion: elementRegion(this.rootRef),
            selfRegion: elementRegion(container),
            alignPoints,
        });
        const { align } = optimalAlignment.alignment;
        this.setState({
            align,
        }, () => {
            this.props.onAlign?.(align);
        });
    }
    onKeyDown(e) {
        if (e.key === "Escape" || e.key === "Tab") {
            this.setState({ isOpen: false });
        }
        if (isEnterKey(e)) {
            this.props.onDateInputKeyDown?.(e);
        }
    }
    handleWrapperClick(e) {
        const { classList } = e.target;
        /**
         * Prevent default fixes bug BB-332 but prevents in closing other dropdowns (Bug BB-1102)
         * so we want to prevent default only when clicking on outside dates in datepicker
         */
        if (e.target && classList?.contains(DATEPICKER_OUTSIDE_DAY_SELECTOR)) {
            e.preventDefault();
        }
    }
    render() {
        const { inputValue, selectedDate, monthDate, isOpen } = this.state;
        const { accessibilityConfig, placeholder, intl, tabIndex } = this.props;
        const classNamesProps = {
            root: this.getOverlayWrapperClasses(),
        };
        return (_jsxs("div", { "data-testid": "datepicker", className: this.getComponentClasses(), ref: this.setComponentRef, onClick: this.handleWrapperClick, children: [
                _jsx("input", { autoComplete: "off", "aria-labelledby": accessibilityConfig?.ariaLabelledBy, "aria-label": accessibilityConfig?.ariaLabel ||
                        intl.formatMessage({ id: "datePicker.accessibility.label" }), "aria-describedby": accessibilityConfig?.ariaDescribedBy, onKeyDown: this.onKeyDown, tabIndex: tabIndex, onClick: () => this.setState({ isOpen: true }), ref: this.inputRef, value: inputValue, className: this.getInputClasses(), placeholder: placeholder, onChange: this.handleInputChanged, onBlur: this.handleInputBlur }), isOpen ? (_jsx("div", { id: `datepicker-popup-${this.datePickerId}`, role: "dialog", ref: this.datePickerContainerRef, children: _jsx(DayPicker, { classNames: classNamesProps, locale: convertLocale(intl.locale), showOutsideDays: true, mode: "single", selected: selectedDate, month: monthDate, onMonthChange: this.handleMonthChanged, weekStartsOn: convertWeekStart(this.props.weekStart ?? "Sunday"), onDayClick: this.handleCustomDayClick }) })) : null, _jsx("span", { className: "gd-datepicker-icon gd-icon-calendar" })
            ] }));
    }
}
const DatePickerWithIntl = injectIntl(WrappedDatePicker);
/**
 * @internal
 */
export class Datepicker extends PureComponent {
    render() {
        return (_jsx(IntlWrapper, { locale: this.props.locale, children: _jsx(DatePickerWithIntl, { ...this.props }) }));
    }
}
