'use client';
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var tslib = require('tslib');
var dateFns = require('date-fns');
var lit = require('lit');
var decorators_js = require('lit/decorators.js');
var live_js = require('lit/directives/live.js');
var ref_js = require('lit/directives/ref.js');
var dropdownElement = require('../../base/dropdown-element.cjs.js');
var time = require('../../utils/time.cjs.js');
var watch = require('../../utils/watch.cjs.js');
var sgdsButton = require('../Button/sgds-button.cjs.js');
var dropdownMenu = require('../Dropdown/dropdown-menu.cjs.js');
var datepickerCalendar = require('./datepicker-calendar.cjs.js');
var datepickerHeader = require('./datepicker-header.cjs.js');
var datepickerInput = require('./datepicker-input.cjs.js');
var datepicker = require('./datepicker.cjs.js');
var sgdsIconButton = require('../IconButton/sgds-icon-button.cjs.js');
var validatorMixin = require('../../utils/validatorMixin.cjs.js');
var ifDefined_js = require('lit/directives/if-defined.js');
var classMap_js = require('lit/directives/class-map.js');
var defaultvalue = require('../../utils/defaultvalue.cjs.js');

/**
 * @summary The `DatePicker` Component is built using `Dropdown`, `Input` and `Button` components. By default, the Calendar points to today's date and input has no value. Users can either pick dates from the calendar or type dates through the input
 *
 * @event sgds-change-date - Emitted when the state of datepicker's input changes during first load, close button reset click & date click. Date values can be accessed via event.target.value
 *
 * @description displayDate sets the month, year views of the calendar while focusedDate follows the focus which also directly changes
 * displayDate on certain occasions. Example, when keyboard moves up to the next month, it updates displayDate which then affect the current
 * date view of the calendar
 */
class SgdsDatepicker extends validatorMixin.SgdsFormValidatorMixin(dropdownElement.DropdownElement) {
    constructor() {
        super();
        /** When true, adds required attribute to input element */
        this.required = false;
        /** When true, adds disabled attribute to input and button element */
        this.disabled = false;
        /** Sets the initial value of the datepicker. Replaces deprecated `initialValue`.
         * Pass in dates in this format `dd/mm/yyyy` for single mode and  `dd/mm/yyyy - dd/mm/yyyy` for range mode
         * For example, `value="22/12/2023"` for single mode or `value="22/12/2023 - 25/12/2023"` for range mode
         */
        this.value = "";
        /**
         * Deprecated since v3.3.0 in favour of `value`.
         * The initial value of DatePicker on first load for single &
         * range mode as array of string. eg.'["22/12/2023"]' for single &
         * '["22/12/2023","25/12/2023"]' for range respectively @deprecated
         * */
        this.initialValue = [];
        this.dateFormat = "DD/MM/YYYY";
        /** ISO date string to set the lowest allowable date value. e.g. "2016-05-19T12:00:00.000Z" */
        this.minDate = "";
        /** ISO date string to set the highest allowable date value. e.g. "2016-05-19T12:00:00.000Z" */
        this.maxDate = "";
        /** Changes DatePicker to single date selection or range date selection */
        this.mode = "single";
        /** Allows invalidFeedback and invalid styles to be visible with the input */
        this.hasFeedback = false;
        /** The datepicker input's label  */
        this.label = "";
        /** The datepicker input's hint text below the label */
        this.hintText = "";
        /** Controls auto-flipping of menu */
        this.noFlip = false;
        /** The drop position of menu relative to the toggle button */
        this.drop = "down";
        /**Gets or sets the default value used to reset this element. The initial value corresponds to the one originally specified in the HTML that created this element. */
        this.defaultValue = "";
        /**@internal */
        this.invalid = false;
        this.view = "days";
        this.selectedDateRange = [];
        this.focusedTabIndex = 3;
        this.initialDisplayDate = new Date();
        this._makeInputValueString = (startDate, endDate, dateFormat) => {
            if (!startDate && !endDate)
                return this.value;
            const formatDate = (date) => dateFns.format(date, time.DATE_PATTERNS[dateFormat].fnsPattern);
            switch (this.mode) {
                case "single": {
                    if (startDate) {
                        this.value = formatDate(startDate);
                    }
                    break;
                }
                case "range": {
                    if (startDate && endDate) {
                        this.value = `${formatDate(startDate)} - ${formatDate(endDate)}`;
                    }
                    if (startDate && !endDate) {
                        this.value = `${formatDate(startDate)} - ${this.dateFormat}`;
                    }
                    break;
                }
            }
            return this.value;
        };
        this._dialogAriaLabels = {
            days: "Choose date",
            months: "Choose month",
            years: "Choose year"
        };
        this.floatingOpts = {
            placement: "bottom-end"
        };
    }
    isValueEmpty() {
        return this.value === "" || this.value === "DD/MM/YYYY" || this.value === "DD/MM/YYYY - DD/MM/YYYY";
    }
    /**
     * Checks for validity. Under the hood, HTMLFormElement's reportValidity method calls this method to check for component's validity state
     * Note that the native error popup is prevented for SGDS form components by default. Instead the validation message shows up in the feedback container of SgdsInput
     */
    reportValidity() {
        return this._mixinReportValidity();
    }
    /**
     * Checks for validity without any native error popup message
     */
    checkValidity() {
        return this._mixinCheckValidity();
    }
    /**
     * Returns the ValidityState object
     */
    get validity() {
        return this._mixinGetValidity();
    }
    /**
     * Returns the validation message based on the ValidityState
     */
    get validationMessage() {
        return this._mixinGetValidationMessage();
    }
    async connectedCallback() {
        super.connectedCallback();
        this.addEventListener("sgds-view", this._handleViewChanged);
        this.addEventListener("sgds-change-calendar", this._handleDateChanged);
        this.addEventListener("sgds-update-focus", this._handleFocusDateChanged);
        this.addEventListener("sgds-selectmonth", this._handleSelectMonth);
        this.addEventListener("sgds-selectyear", this._handleSelectYear);
        this.addEventListener("sgds-selectdates", this._handleSelectDatesAndClose);
        this.addEventListener("sgds-selectdates-input", this._handleSelectDatesInput);
        this.addEventListener("sgds-empty-input", this._handleEmptyInput);
        this.addEventListener("keydown", this._handleTab);
        this.addEventListener("sgds-hide", this._handleCloseMenu);
        this.addEventListener("sgds-show", this._handleOpenMenu);
        this.addEventListener("blur", this._mixinCheckValidity);
        this.initialValue = this.value ? this.value.split(" - ").map(v => v.trim()) : this.initialValue;
        this.initialDisplayDate = this.displayDate || new Date();
        if (this.initialValue && this.initialValue.length > 0) {
            // Validate initialValue against the dateFormat regex
            const dateFormatRegex = new RegExp(this._getDateFormatRegex());
            // const startDateString = this.initialValue[0];
            const invalidDates = this.initialValue.filter(v => !dateFormatRegex.test(v));
            if (invalidDates.length > 0) {
                return console.error("Invalid date format in initialValue:", invalidDates);
            }
            else {
                const initialSelectedDates = this.initialValue.map(v => time.setTimeToNoon(dateFns.parse(v, time.DATE_PATTERNS[this.dateFormat].fnsPattern, new Date())));
                this._handleSelectDates(initialSelectedDates);
            }
        }
        else {
            this.displayDate = this.initialDisplayDate;
        }
    }
    async firstUpdated(changedProperties) {
        super.firstUpdated(changedProperties);
        if (this.menuIsOpen) {
            const input = await this.datepickerInputAsync;
            this.showMenu();
            const cal = await this.calendar;
            cal.focusOnCalendar(input);
        }
    }
    /** @internal */
    _getDateFormatRegex() {
        // validate date strings and adhere to the specified date format
        return (this.dateFormat
            // Replace any special characters with their escaped version using "\\$&"
            .replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
            // Replace 'MM' with '\\d{2}', which matches two digits representing the month (e.g., 01, 12)
            .replace("MM", "\\d{2}")
            // Replace 'DD' with '\\d{2}', which matches two digits representing the day (e.g., 01, 31)
            .replace("DD", "\\d{2}")
            // Replace 'YYYY' with '\\d{4}', which matches four digits representing the year (e.g., 2021)
            .replace("YYYY", "\\d{4}")
            // Replace '/' with '\\/', which matches the forward slash character
            .replace("/", "\\/"));
    }
    _handleTab(event) {
        if (!this.menuIsOpen) {
            return;
        }
        const tabIndexArray = Array(4);
        if (event.shiftKey && event.key === "Tab") {
            event.preventDefault();
            this.focusedTabIndex = (this.focusedTabIndex - 1 + tabIndexArray.length) % tabIndexArray.length;
        }
        else if (event.key === "Tab") {
            event.preventDefault();
            this.focusedTabIndex = (this.focusedTabIndex + 1 + tabIndexArray.length) % tabIndexArray.length;
        }
    }
    _handleValueChange() {
        this.emit("sgds-change-date");
    }
    async _handleCloseMenu() {
        //return focus to input when menu closes
        const input = await this.datepickerInputAsync;
        input.focus();
        if (this.selectedDateRange.length === 0) {
            this.displayDate = this.initialDisplayDate;
        }
        else {
            const selectedDatesLength = this.selectedDateRange.length;
            this.displayDate = this.selectedDateRange[selectedDatesLength - 1];
            const calendar = await this.calendar;
            calendar._updateFocusedDate();
        }
    }
    async _handleOpenMenu() {
        const cal = await this.calendar;
        const input = await this.datepickerInputAsync;
        cal.focusOnCalendar(input);
    }
    _handleSelectDatesInput(event) {
        this._handleSelectDates(event.detail);
    }
    async _handleSelectDates(newSelectedDates) {
        newSelectedDates.sort((a, b) => a.getTime() - b.getTime());
        this.displayDate = newSelectedDates[0];
        this.focusedDate = newSelectedDates[0];
        this.selectedDateRange = newSelectedDates;
        // Get the formattedDate value for the selected dates
        const formattedDate = this._makeInputValueString(this.selectedDateRange[0], this.selectedDateRange[1], this.dateFormat);
        // Set formattedDate value as the new value for sgds-input
        this.value = formattedDate;
        const input = await this.datepickerInputAsync;
        input.updateMaskValue();
        this._manageInternalsValid();
    }
    async _handleSelectDatesAndClose(event) {
        await this._handleSelectDates(event.detail);
        if (this.mode === "range" && this.selectedDateRange.length === 2) {
            this.hideMenu();
        }
        else if (this.mode === "single" && this.selectedDateRange.length === 1) {
            this.hideMenu();
        }
    }
    /** update latest view state from datepicker-header */
    _handleViewChanged(event) {
        this.view = event.detail;
    }
    _handleDateChanged(event) {
        this.displayDate = event.detail;
    }
    _handleFocusDateChanged(event) {
        this.focusedDate = event.detail;
    }
    _handleSelectMonth(event) {
        this.displayDate = event.detail;
    }
    _handleSelectYear(event) {
        this.displayDate = event.detail;
    }
    async _handleInvalidInput() {
        this.selectedDateRange = [];
        this.displayDate = this.initialDisplayDate;
        this.invalid = true;
        this._manageInternalsBadInput();
    }
    async _handleEmptyInput() {
        if (this.required) {
            this._manageEmptyInput();
        }
        return;
    }
    async _resetDatepicker(resetValue = "") {
        this.displayDate = this.initialDisplayDate;
        this.selectedDateRange = [];
        this.value = resetValue;
        this.view = "days";
        const input = await this.datepickerInputAsync;
        input.setInvalid(false);
        input.destroyInputMask();
        await input.applyInputMask();
        this._mixinResetValidity(input);
        if (this.isValueEmpty()) {
            this._handleEmptyInput();
        }
    }
    _manageInternalsBadInput() {
        this._mixinSetValidity({
            badInput: true
        }, "Invalid date input", this.datepickerInput);
    }
    /**
     * Even though element internals handles the required constraint validation. This custom one is still needed as
     * datepicker input has a special case where the default input mask "DD/MM/YYYY" means an empty input.
     * However, the required constraint validation sees "DD/MM/YYYY" as a non-empty input.
     */
    _manageEmptyInput() {
        this._mixinSetValidity({
            valueMissing: true
        }, "Please fill in this field", this.datepickerInput);
    }
    /**
     * Called when a valid date is entered via input or selected by calendar
     * 1. sets validity state to valid
     * 2. updates invalid prop
     * 3. sets the form value of datepicker
     */
    _manageInternalsValid() {
        this._mixinSetValidity({});
        this.invalid = this.datepickerInput.invalid = false;
        this._mixinSetFormValue();
    }
    /**
     * Handles the form "reset" event
     */
    async _mixinResetFormControl() {
        this._resetDatepicker(this.defaultValue);
    }
    async _handleInputMaskChange(e) {
        this.value = e.detail;
        if (this.isValueEmpty()) {
            this._resetDatepicker();
        }
    }
    render() {
        return lit.html `
      <div class="datepicker-container">
        <sgds-datepicker-input
          .value=${live_js.live(this.value)}
          ?required=${this.required}
          ?disabled=${this.disabled}
          placeholder=${this.mode === "single" ? "DD/MM/YYYY" : "DD/MM/YYYY - DD/MM/YYYY"}
          mode=${this.mode}
          invalidFeedback=${ifDefined_js.ifDefined(this.invalidFeedback ? this.invalidFeedback : this._mixinGetValidationMessage())}
          @sgds-mask-input-change=${this._handleInputMaskChange}
          @sgds-invalid-input=${this._handleInvalidInput}
          minDate=${this.minDate}
          maxDate=${this.maxDate}
          label=${this.label}
          hintText=${this.hintText}
          name=${this.name}
          ?invalid=${this.invalid}
          hasFeedback=${ifDefined_js.ifDefined(this.hasFeedback ? "both" : undefined)}
          ?readonly=${this.readonly}
        >
        </sgds-datepicker-input>
        <sgds-icon-button
          ${ref_js.ref(this.myDropdown)}
          role="button"
          class=${classMap_js.classMap({
            "calendar-btn": true,
            "with-hint-text": this.hintText || this.invalid,
            "with-label": this.label
        })}
          aria-expanded="${this.menuIsOpen}"
          aria-haspopup="dialog"
          aria-controls=${this.dropdownMenuId}
          @click=${() => this.toggleMenu()}
          ariaLabel=${this.menuIsOpen ? "Close Calendar" : "Open Calendar"}
          ?disabled=${this.disabled || this.readonly}
          variant="outline"
          name="calendar"
        >
        </sgds-icon-button>
        <ul
          id=${this.dropdownMenuId}
          class="sgds datepicker dropdown-menu"
          role="dialog"
          aria-label=${this._dialogAriaLabels[this.view]}
          @click=${(event) => event.stopPropagation()}
          ${ref_js.ref(this.menuRef)}
        >
          <sgds-datepicker-header
            .view=${this.view}
            .displayDate=${this.displayDate}
            .focusedDate=${this.focusedDate}
            .selectedDate=${this.selectedDateRange}
            .focusedTabIndex=${this.focusedTabIndex}
          ></sgds-datepicker-header>
          <sgds-datepicker-calendar
            .show=${this.menuIsOpen}
            .view=${this.view}
            .displayDate=${this.displayDate}
            .mode=${this.mode}
            minDate=${this.minDate}
            maxDate=${this.maxDate}
            .selectedDate=${this.selectedDateRange}
            .focusedTabIndex=${this.focusedTabIndex}
          ></sgds-datepicker-calendar>
        </ul>
      </div>
    `;
    }
}
SgdsDatepicker.styles = [...dropdownElement.DropdownElement.styles, dropdownMenu["default"], datepicker["default"]];
/**@internal */
SgdsDatepicker.dependencies = {
    "sgds-datepicker-input": datepickerInput.DatepickerInput,
    "sgds-datepicker-calendar": datepickerCalendar.DatepickerCalendar,
    "sgds-datepicker-header": datepickerHeader.DatepickerHeader,
    "sgds-button": sgdsButton.SgdsButton,
    "sgds-icon-button": sgdsIconButton.SgdsIconButton
};
tslib.__decorate([
    decorators_js.property({ type: Boolean, reflect: true })
], SgdsDatepicker.prototype, "required", void 0);
tslib.__decorate([
    decorators_js.property({ reflect: true })
], SgdsDatepicker.prototype, "name", void 0);
tslib.__decorate([
    decorators_js.property({ type: Boolean, reflect: true })
], SgdsDatepicker.prototype, "disabled", void 0);
tslib.__decorate([
    decorators_js.property({ type: String, reflect: true })
], SgdsDatepicker.prototype, "value", void 0);
tslib.__decorate([
    decorators_js.property({ type: Array, reflect: true })
], SgdsDatepicker.prototype, "initialValue", void 0);
tslib.__decorate([
    decorators_js.property({ type: String })
], SgdsDatepicker.prototype, "minDate", void 0);
tslib.__decorate([
    decorators_js.property({ type: String })
], SgdsDatepicker.prototype, "maxDate", void 0);
tslib.__decorate([
    decorators_js.property({ type: String, reflect: true })
], SgdsDatepicker.prototype, "mode", void 0);
tslib.__decorate([
    decorators_js.property({ type: String, reflect: true })
], SgdsDatepicker.prototype, "invalidFeedback", void 0);
tslib.__decorate([
    decorators_js.property({ type: Boolean, reflect: true })
], SgdsDatepicker.prototype, "hasFeedback", void 0);
tslib.__decorate([
    decorators_js.property({ reflect: true })
], SgdsDatepicker.prototype, "label", void 0);
tslib.__decorate([
    decorators_js.property({ reflect: true })
], SgdsDatepicker.prototype, "hintText", void 0);
tslib.__decorate([
    decorators_js.property({ type: Boolean, reflect: true, state: false })
], SgdsDatepicker.prototype, "noFlip", void 0);
tslib.__decorate([
    decorators_js.property({ type: String, reflect: true, state: false })
], SgdsDatepicker.prototype, "drop", void 0);
tslib.__decorate([
    decorators_js.property({ attribute: false })
], SgdsDatepicker.prototype, "displayDate", void 0);
tslib.__decorate([
    defaultvalue.defaultValue()
], SgdsDatepicker.prototype, "defaultValue", void 0);
tslib.__decorate([
    decorators_js.state()
], SgdsDatepicker.prototype, "invalid", void 0);
tslib.__decorate([
    decorators_js.state()
], SgdsDatepicker.prototype, "view", void 0);
tslib.__decorate([
    decorators_js.state()
], SgdsDatepicker.prototype, "selectedDateRange", void 0);
tslib.__decorate([
    decorators_js.state()
], SgdsDatepicker.prototype, "focusedDate", void 0);
tslib.__decorate([
    decorators_js.state()
], SgdsDatepicker.prototype, "focusedTabIndex", void 0);
tslib.__decorate([
    decorators_js.queryAsync("sgds-datepicker-calendar")
], SgdsDatepicker.prototype, "calendar", void 0);
tslib.__decorate([
    decorators_js.queryAsync("sgds-datepicker-input")
], SgdsDatepicker.prototype, "datepickerInputAsync", void 0);
tslib.__decorate([
    decorators_js.query("sgds-datepicker-input")
], SgdsDatepicker.prototype, "datepickerInput", void 0);
tslib.__decorate([
    watch.watch("value", { waitUntilFirstUpdate: true })
], SgdsDatepicker.prototype, "_handleValueChange", null);

exports.SgdsDatepicker = SgdsDatepicker;
exports["default"] = SgdsDatepicker;
//# sourceMappingURL=sgds-datepicker.cjs.js.map
