/*!
 * Custom elements for Nanopore-Digital Web applications
 */
import { Host, } from "@stencil/core";
import { h } from "../../utils/renderer";
import { addDays, startOfWeek, endOfWeek, setMonth, setYear, dateClamp, inRange, endOfMonth, startOfMonth, printISODate, parseISODate, DaysOfWeek, } from "../../utils/date-utils";
import defaultLocalization from "./duet-date-picker/date-localization";
import { DatePickerMonth } from "./duet-date-picker/date-picker-month";
function range(from, to) {
    const result = [];
    for (let i = from; i <= to; i++) {
        result.push(i);
    }
    return result;
}
let dateIds = 0;
/**
 * Enables the selection of a date from a calendar view.
 *
 * @version 1.0.0
 * @status stable
 */
export class NanoDatePicker {
    dateId = `nano-datepicker-${dateIds++}`;
    monthSelectId = this.dateId + '-' + 'month';
    yearSelectId = this.dateId + '-' + 'year';
    dialogLabelId = this.dateId + '-' + 'dialog';
    firstFocusableElement;
    monthSelectNode;
    focusedDayNode;
    focusTimeoutId;
    initialTouchX = null;
    initialTouchY = null;
    host;
    activeFocus = false;
    focusedDay = new Date();
    // Public Property API
    _selectedDate = '';
    /** Selected Date. Must be in IS0-8601 format: YYYY-MM-DD. */
    get selectedDate() {
        return this._selectedDate;
    }
    set selectedDate(value) {
        if (typeof value !== 'string') {
            this._selectedDate = '';
            return;
        }
        value = value.split('T')[0]; // remove time part if present
        this._selectedDate = value;
    }
    /** Minimum date allowed to be picked. Must be in IS0-8601 format: YYYY-MM-DD.
     * This setting can be used alone or together with the max property. */
    min = '';
    /** Maximum date allowed to be picked. Must be in IS0-8601 format: YYYY-MM-DD.
     * This setting can be used alone or together with the min property. */
    max = '';
    /** Which day is considered first day of the week. `0` for Sunday, `1` for Monday, etc.
     * Default is Monday. */
    firstDayOfWeek = DaysOfWeek.Monday;
    /** Button labels, day names, month names, etc, used for localization.
     * Default is English. */
    localization = defaultLocalization;
    /**
     * Controls which days are disabled and therefore disallowed.
     * For example, this can be used to disallow selection of weekends.
     * @returns `false` for not disabled and `true` for disabled
     */
    isDateDisabled = () => false;
    /** Controls whether the picker is within a modal control and will therefore keep focus within the control */
    isModal = false;
    /** @internal First focusable element. Relevant for keeping focus within the control */
    firstFocusEle;
    // Events
    /** Event emitted when a date is selected. */
    nanoDatePicked;
    // Public methods API
    /** Focus to the calendar
     * @param day - focus to the current selected day
     */
    async setFocus(day = false) {
        this.setFocusedDay(parseISODate(this.selectedDate) || new Date());
        if (day) {
            setTimeout((_) => this.focusedDayNode.focus({ preventScroll: true }), 20);
            return;
        }
        clearTimeout(this.focusTimeoutId);
        this.focusTimeoutId = setTimeout(() => {
            this.monthSelectNode.focus({ preventScroll: true });
        }, 20);
    }
    handleSelectedDateChange() {
        this.setFocus(true);
    }
    // Local methods.
    enableActiveFocus = () => {
        this.activeFocus = true;
    };
    disableActiveFocus = () => {
        this.activeFocus = false;
    };
    addDays(days) {
        this.setFocusedDay(addDays(this.focusedDay, days));
    }
    addMonths(months) {
        this.setMonth(this.focusedDay.getMonth() + months);
    }
    addYears(years) {
        this.setYear(this.focusedDay.getFullYear() + years);
    }
    startOfWeek() {
        this.setFocusedDay(startOfWeek(this.focusedDay, this.firstDayOfWeek));
    }
    endOfWeek() {
        this.setFocusedDay(endOfWeek(this.focusedDay, this.firstDayOfWeek));
    }
    setMonth(month) {
        const min = setMonth(startOfMonth(this.focusedDay), month);
        const max = endOfMonth(min);
        const date = setMonth(this.focusedDay, month);
        this.setFocusedDay(dateClamp(date, min, max));
    }
    setYear(year) {
        const min = setYear(startOfMonth(this.focusedDay), year);
        const max = endOfMonth(min);
        const date = setYear(this.focusedDay, year);
        this.setFocusedDay(dateClamp(date, min, max));
    }
    setFocusedDay(day) {
        this.focusedDay = dateClamp(day, parseISODate(this.min), parseISODate(this.max));
    }
    handleTouchStart = (event) => {
        const touch = event.changedTouches[0];
        this.initialTouchX = touch.pageX;
        this.initialTouchY = touch.pageY;
    };
    handleTouchMove = (event) => {
        event.preventDefault();
    };
    handleTouchEnd = (event) => {
        const touch = event.changedTouches[0];
        const distX = touch.pageX - this.initialTouchX; // get horizontal dist traveled
        const distY = touch.pageY - this.initialTouchY; // get vertical dist traveled
        const threshold = 70;
        const isHorizontalSwipe = Math.abs(distX) >= threshold && Math.abs(distY) <= threshold;
        if (isHorizontalSwipe) {
            this.addMonths(distX < 0 ? 1 : -1);
        }
        this.initialTouchY = null;
        this.initialTouchX = null;
    };
    handleNextMonthClick = (event) => {
        event.preventDefault();
        this.addMonths(1);
    };
    handlePreviousMonthClick = (event) => {
        event.preventDefault();
        this.addMonths(-1);
    };
    handleKeyboardNavigation = (event) => {
        // handle tab separately, since it needs to be treated
        // differently to other keyboard interactions
        if (event.key === 'Tab' && !event.shiftKey && this.isModal) {
            event.preventDefault();
            const ele = this.firstFocusEle || this.firstFocusableElement;
            if (ele['setFocus'])
                ele.setFocus();
            else
                ele.focus();
            return;
        }
        let handled = true;
        switch (event.key) {
            case 'ArrowRight':
                this.addDays(1);
                break;
            case 'ArrowLeft':
                this.addDays(-1);
                break;
            case 'ArrowDown':
                this.addDays(7);
                break;
            case 'ArrowUp':
                this.addDays(-7);
                break;
            case 'PageUp':
                if (event.shiftKey) {
                    this.addYears(-1);
                }
                else {
                    this.addMonths(-1);
                }
                break;
            case 'PageDown':
                if (event.shiftKey) {
                    this.addYears(1);
                }
                else {
                    this.addMonths(1);
                }
                break;
            case 'Home':
                this.startOfWeek();
                break;
            case 'End':
                this.endOfWeek();
                break;
            default:
                handled = false;
        }
        if (handled) {
            event.preventDefault();
            this.enableActiveFocus();
        }
    };
    handleDaySelect = (_event, day) => {
        const isAllowed = !this.isDateDisabled(day);
        const isInRange = inRange(day, parseISODate(this.min), parseISODate(this.max));
        if (!isInRange || !isAllowed) {
            return;
        }
        if (day.getMonth() === this.focusedDay.getMonth()) {
            this.setValue(day);
        }
        else {
            this.setFocusedDay(day);
        }
    };
    handleMonthSelect = (e) => {
        this.setMonth(parseInt(e.target.value, 10));
    };
    handleYearSelect = (e) => {
        this.setYear(parseInt(e.target.value, 10));
    };
    setValue(date) {
        this.selectedDate = printISODate(date);
        this.nanoDatePicked.emit({
            value: this.selectedDate,
            valueAsDate: date,
        });
    }
    processFocusedDayNode = (element) => {
        this.focusedDayNode = element;
        if (this.activeFocus) {
            setTimeout(() => element.focus({ preventScroll: true }), 0);
        }
    };
    componentWillLoad() {
        this.handleSelectedDateChange();
    }
    render() {
        const valueAsDate = parseISODate(this.selectedDate);
        const selectedYear = (valueAsDate || this.focusedDay).getFullYear();
        const focusedMonth = this.focusedDay.getMonth();
        const focusedYear = this.focusedDay.getFullYear();
        const minDate = parseISODate(this.min);
        const maxDate = parseISODate(this.max);
        const prevMonthDisabled = minDate != null &&
            minDate.getMonth() === focusedMonth &&
            minDate.getFullYear() === focusedYear;
        const nextMonthDisabled = maxDate != null &&
            maxDate.getMonth() === focusedMonth &&
            maxDate.getFullYear() === focusedYear;
        let minYear = selectedYear - 10;
        let maxYear = selectedYear + 10;
        if (minDate)
            minYear = minDate.getFullYear();
        if (maxDate)
            maxYear = maxDate.getFullYear();
        return (h(Host, { key: 'd3b992532815f40dbd9fcb7b0ddfaa27d703899b', class: "nano-date-picker" }, h("div", { key: 'c1be847528fac0224c54110aa77a7b471969c34d', class: "duet-date" }, h("div", { key: '8aa8dceea4d272bf171924bfbf59a4f645bed23d', class: {
                'duet-date__dialog': true,
                'is-active': true,
            }, onTouchMove: this.handleTouchMove, onTouchStart: this.handleTouchStart, onTouchEnd: this.handleTouchEnd }, h("div", { key: '374d34c706b3972971ebfc05d32d4f2cfb8721de', class: "duet-date__dialog-content" }, h("div", { key: '4713d41d66ae2bc7057ec337080cd54700b51033', class: "duet-date__vhidden duet-date__instructions", "aria-live": "polite" }, this.localization.keyboardInstruction), h("div", { key: 'b602d630814a176a066dab67645178fd15a7ca22', class: "duet-date__header", onFocusin: this.disableActiveFocus }, h("div", { key: '2cc00317d6468c2ea5c477249774eec83f5b94da' }, h("h2", { key: '0f420e58b51e50d6d973330665c508ca8167f3c3', id: this.dialogLabelId, class: "duet-date__vhidden", "aria-live": "polite" }, this.localization.monthNames[focusedMonth], ' ', this.focusedDay.getFullYear()), h("label", { key: '56192f5d51880678b86137cca89cb4a98515e03e', htmlFor: this.monthSelectId, class: "duet-date__vhidden" }, this.localization.monthSelectLabel), h("div", { key: '900c7995f2a3c51b5d22fee6176518940e7efc27', class: "duet-date__select" }, h("select", { key: '22bef19a74a42e7864d6cff32707adc7c0cc52b6', id: this.monthSelectId, class: "duet-date__select--month", ref: (element) => (this.firstFocusableElement = this.monthSelectNode =
                element), onChange: this.handleMonthSelect }, this.localization.monthNames.map((month, i) => (h("option", { key: month, value: i, selected: i === focusedMonth, disabled: !inRange(new Date(focusedYear, i, 1), minDate ? startOfMonth(minDate) : null, maxDate ? endOfMonth(maxDate) : null) }, month)))), h("div", { key: 'ba1108c781547bbaba8da184f07fd7d36b12cb4c', class: "duet-date__select-label", "aria-hidden": "true" }, h("span", { key: '8cd8018d9b68129e69219b9bffcd978a394269a5' }, this.localization.monthNamesShort[focusedMonth]), h("nano-icon", { key: 'e2b440b6adf599930d20e584c92c5f66d52564fd', name: "light/chevron-down" }))), h("label", { key: '043530523830f9c5a67682c47cfb6dc0464f3133', htmlFor: this.yearSelectId, class: "duet-date__vhidden" }, this.localization.yearSelectLabel), h("div", { key: 'e2e43ad2280255eb46cf06b9328af9bbefbe96a5', class: "duet-date__select" }, h("select", { key: 'eaaa7a64a034d1723c20b42cf14c8cea0f8c78b1', id: this.yearSelectId, class: "duet-date__select--year", onChange: this.handleYearSelect }, range(minYear, maxYear).map((year) => (h("option", { key: year, selected: year === focusedYear }, year)))), h("div", { key: 'c6ce7ec0f073caad0bd6ab09d03f1e20f5433e97', class: "duet-date__select-label", "aria-hidden": "true" }, h("span", { key: '3f985ad9b631cb140f4e551ab2f20cbdef88da6a' }, this.focusedDay.getFullYear()), h("nano-icon", { key: '1b06f5e08b8b33bcc7d641abe122e3bb0fad05ba', name: "light/chevron-down" })))), h("div", { key: '4ea6d2f63406af47b4aba3b137c778aa9fed2524', class: "duet-date__nav" }, h("button", { key: '6cd94d90c91aa0ccbd74a9ba361b7d894ef9fccf', class: "duet-date__prev", onClick: this.handlePreviousMonthClick, disabled: prevMonthDisabled, type: "button" }, h("nano-icon", { key: '83d33e3779d7249634afbd8f1cd1c6476bad4697', name: "light/chevron-left" }), h("span", { key: '2318ef1d137f42a93240efc4caa15b376f079909', class: "duet-date__vhidden" }, this.localization.prevMonthLabel)), h("button", { key: 'f8c400b9e644687ce1279a70e6fc5c0b1aec6aac', class: "duet-date__next", onClick: this.handleNextMonthClick, disabled: nextMonthDisabled, type: "button" }, h("nano-icon", { key: 'a4ef19900fdfee3cc340ab6bba83dcf782e9b21a', name: "light/chevron-right" }), h("span", { key: 'da03dc579aeb3dc5a00b2c647f78b71186cc7d74', class: "duet-date__vhidden" }, this.localization.nextMonthLabel)))), h("div", { key: 'ec82cee96b94f0b4a36993d42bae2eb02a392522' }, h(DatePickerMonth, { key: 'e84697f87540abe1e8cfd47903655ac8b68ba08d', selectedDate: valueAsDate, focusedDate: this.focusedDay, onDateSelect: this.handleDaySelect, onKeyboardNavigation: this.handleKeyboardNavigation, labelledById: this.dialogLabelId, localization: this.localization, firstDayOfWeek: this.firstDayOfWeek, focusedDayRef: this.processFocusedDayNode, min: minDate, max: maxDate, isDateDisabled: this.isDateDisabled })))))));
    }
    static get is() { return "nano-date-picker"; }
    static get encapsulation() { return "shadow"; }
    static get originalStyleUrls() {
        return {
            "$": ["date-picker.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["date-picker.css"]
        };
    }
    static get properties() {
        return {
            "selectedDate": {
                "type": "string",
                "attribute": "selected-date",
                "mutable": true,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Selected Date. Must be in IS0-8601 format: YYYY-MM-DD."
                },
                "getter": true,
                "setter": true,
                "reflect": false,
                "defaultValue": "''"
            },
            "min": {
                "type": "string",
                "attribute": "min",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Minimum date allowed to be picked. Must be in IS0-8601 format: YYYY-MM-DD.\nThis setting can be used alone or together with the max property."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "''"
            },
            "max": {
                "type": "string",
                "attribute": "max",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Maximum date allowed to be picked. Must be in IS0-8601 format: YYYY-MM-DD.\nThis setting can be used alone or together with the min property."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "''"
            },
            "firstDayOfWeek": {
                "type": "number",
                "attribute": "first-day-of-week",
                "mutable": false,
                "complexType": {
                    "original": "DaysOfWeek",
                    "resolved": "DaysOfWeek.Friday | DaysOfWeek.Monday | DaysOfWeek.Saturday | DaysOfWeek.Sunday | DaysOfWeek.Thursday | DaysOfWeek.Tuesday | DaysOfWeek.Wednesday",
                    "references": {
                        "DaysOfWeek": {
                            "location": "import",
                            "path": "../../utils/date-utils",
                            "id": "src/utils/date-utils.ts::DaysOfWeek"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Which day is considered first day of the week. `0` for Sunday, `1` for Monday, etc.\nDefault is Monday."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "DaysOfWeek.Monday"
            },
            "localization": {
                "type": "unknown",
                "attribute": "localization",
                "mutable": false,
                "complexType": {
                    "original": "DuetLocalizedText",
                    "resolved": "{ buttonLabel: string; placeholder: string; selectedDateMessage: string; prevMonthLabel: string; nextMonthLabel: string; monthSelectLabel: string; yearSelectLabel: string; closeLabel: string; keyboardInstruction: string; calendarHeading: string; dayNames: DayNames; monthNames: MonthsNames; monthNamesShort: MonthsNames; }",
                    "references": {
                        "DuetLocalizedText": {
                            "location": "import",
                            "path": "./duet-date-picker/date-localization",
                            "id": "src/components/date-picker/duet-date-picker/date-localization.ts::DuetLocalizedText"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Button labels, day names, month names, etc, used for localization.\nDefault is English."
                },
                "getter": false,
                "setter": false,
                "defaultValue": "defaultLocalization"
            },
            "isDateDisabled": {
                "type": "unknown",
                "attribute": "is-date-disabled",
                "mutable": false,
                "complexType": {
                    "original": "DateDisabledPredicate",
                    "resolved": "(date: Date) => boolean",
                    "references": {
                        "DateDisabledPredicate": {
                            "location": "import",
                            "path": "./date-picker-interface",
                            "id": "src/components/date-picker/date-picker-interface.d.ts::DateDisabledPredicate"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "returns",
                            "text": "`false` for not disabled and `true` for disabled"
                        }],
                    "text": "Controls which days are disabled and therefore disallowed.\nFor example, this can be used to disallow selection of weekends."
                },
                "getter": false,
                "setter": false,
                "defaultValue": "() => false"
            },
            "isModal": {
                "type": "boolean",
                "attribute": "is-modal",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Controls whether the picker is within a modal control and will therefore keep focus within the control"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "false"
            },
            "firstFocusEle": {
                "type": "unknown",
                "attribute": "first-focus-ele",
                "mutable": false,
                "complexType": {
                    "original": "HTMLElement",
                    "resolved": "HTMLElement",
                    "references": {
                        "HTMLElement": {
                            "location": "global",
                            "id": "global::HTMLElement"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "internal",
                            "text": "First focusable element. Relevant for keeping focus within the control"
                        }],
                    "text": ""
                },
                "getter": false,
                "setter": false
            }
        };
    }
    static get states() {
        return {
            "activeFocus": {},
            "focusedDay": {}
        };
    }
    static get events() {
        return [{
                "method": "nanoDatePicked",
                "name": "nanoDatePicked",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Event emitted when a date is selected."
                },
                "complexType": {
                    "original": "PickerChangeEvent",
                    "resolved": "{ valueAsDate: Date; value: string; }",
                    "references": {
                        "PickerChangeEvent": {
                            "location": "import",
                            "path": "./date-picker-interface",
                            "id": "src/components/date-picker/date-picker-interface.d.ts::PickerChangeEvent"
                        }
                    }
                }
            }];
    }
    static get methods() {
        return {
            "setFocus": {
                "complexType": {
                    "signature": "(day?: boolean) => Promise<void>",
                    "parameters": [{
                            "name": "day",
                            "type": "boolean",
                            "docs": "- focus to the current selected day"
                        }],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "Focus to the calendar",
                    "tags": [{
                            "name": "param",
                            "text": "day - focus to the current selected day"
                        }]
                }
            }
        };
    }
    static get elementRef() { return "host"; }
    static get watchers() {
        return [{
                "propName": "selectedDate",
                "methodName": "handleSelectedDateChange"
            }];
    }
}
