/**
 * @license
 * Copyright 2023 Tyler Technologies, Inc. 
 * License: Apache-2.0
 */
import { isValidDate, Platform } from '@tylertech/forge-core';
import { CALENDAR_CONSTANTS } from '../../calendar/calendar-constants';
import { formatDate, parseDateString } from '../../core/utils/date-utils';
import { DEFAULT_DATE_MASK } from '../../core/mask/date-input-mask';
import { BASE_DATE_PICKER_CONSTANTS } from './base-date-picker-constants';
export class BaseDatePickerFoundation {
    constructor(_adapter) {
        this._adapter = _adapter;
        this._value = null;
        this._min = null;
        this._max = null;
        this._disabledDates = null;
        this._open = false;
        this._disabled = false;
        this._masked = true;
        this._maskFormat = DEFAULT_DATE_MASK;
        this._showMaskFormat = false;
        this._valueMode = 'object';
        this._notifyInputValueChanges = true;
        this._allowInvalidDate = false;
        this._showToday = false;
        this._showClear = false;
        this._yearRange = '-50:+50';
        this._isInitialized = false;
        this._inputListener = evt => this._onInput(evt);
        this._inputKeydownListener = evt => this._onInputKeydown(evt);
        this._inputFocusListener = evt => this._onInputFocus(evt);
        this._inputBlurListener = evt => this._onInputBlur(evt);
        this._inputValueChangedListener = value => this._onInputValueChanged(value);
        this._dropdownCloseListener = () => this._onDropdownClose();
        this._toggleMousedownListener = evt => this._onToggleMousedown(evt);
        this._dateSelectListener = evt => this._onDateSelected(evt.detail);
        this._monthChangeListener = evt => this._onMonthChanged(evt);
        this._activeChangeListener = id => this._onActiveDescendantChanged(id);
        this._todayListener = () => this._onToday();
        this._clearListener = () => this._onClear();
    }
    initialize() {
        var _a;
        this._adapter.initialize();
        this._adapter.initializeAccessibility();
        if (!this._adapter.hasInputElement()) {
            throw new Error('Unable to locate child <input> element.');
        }
        (_a = this._initializeState) === null || _a === void 0 ? void 0 : _a.call(this);
        this._adapter.tryCreateToggle();
        if (!this._masked) {
            this._setInputChangeListeners();
        }
        this._initializeListeners();
        this._setFormattedInputValue(true);
        this._applyDisabled();
        this._applyMask();
        this._initializeValueChangedListeners();
        this._isInitialized = true;
    }
    disconnect() {
        this._isInitialized = false;
        this._destroyListeners();
        if (this._masked) {
            this._adapter.destroyMask();
        }
        this._closeCalendar();
        this._adapter.destroy();
    }
    _initializeListeners() {
        this._adapter.addToggleListener('mousedown', this._toggleMousedownListener);
        this._adapter.addInputListener('keydown', this._inputKeydownListener, true);
        this._adapter.addInputListener('focus', this._inputFocusListener);
        this._adapter.addInputListener('blur', this._inputBlurListener);
    }
    _initializeValueChangedListeners() {
        this._adapter.setInputValueChangedListener(this, this._inputValueChangedListener);
    }
    _setInputChangeListeners() {
        this._adapter.addInputListener('input', this._inputListener);
    }
    _removeInputChangeListeners() {
        this._adapter.removeInputListener('input', this._inputListener);
    }
    _destroyListeners() {
        this._adapter.removeToggleListener('mousedown', this._toggleMousedownListener);
        this._adapter.removeInputListener('keydown', this._inputKeydownListener, true);
        this._adapter.removeInputListener('focus', this._inputFocusListener);
        this._adapter.removeInputListener('blur', this._inputBlurListener);
        this._removeInputChangeListeners();
    }
    _onInput(evt) {
        this._handleInput(this._adapter.getInputValue());
    }
    _onInputFocus(evt) {
        if (this.masked && this.showMaskFormat) {
            this._applyMask();
        }
        this._adapter.selectInputText();
    }
    _onInputBlur(evt) {
        if (this.masked && this.showMaskFormat) {
            this._applyMask();
        }
        this._formatInputValue();
        if (this._open && !this._adapter.isInputFocused()) {
            this._closeCalendar(true);
        }
    }
    _openCalendar(emitOpenEvent = false) {
        this._formatInputValue();
        const calendarConfig = {
            mode: this._mode,
            value: this._getCurrentValue(),
            min: this._min,
            max: this._max,
            disabledDates: this._disabledDates,
            yearRange: this._yearRange,
            todayButton: this._showToday,
            clearButton: this._showClear,
            todayCallback: this._todayListener,
            clearCallback: this._clearListener,
            disabledDateBuilder: this._disableDayCallback,
            disabledDaysOfWeek: this._disabledDaysOfWeek,
            preventFocus: true,
            menuAnimation: 'fade',
            fixedHeight: true,
            selectionFollowsMonth: true,
            locale: this._locale
        };
        const dropdownConfig = {
            popupClasses: this._popupClasses,
            closeCallback: this._dropdownCloseListener,
            activeChangeCallback: this._activeChangeListener
        };
        // If the max date is in the past, set the calendar to the min date
        const currentDate = new Date().getTime();
        if (this._min && this._max && this._max.getTime() < currentDate) {
            calendarConfig.year = this._min.getFullYear();
            calendarConfig.month = this._min.getMonth();
        }
        this._adapter.attachCalendar(calendarConfig, dropdownConfig);
        this._adapter.addCalendarListener(CALENDAR_CONSTANTS.events.DATE_SELECT, this._dateSelectListener);
        this._adapter.addCalendarListener(CALENDAR_CONSTANTS.events.MONTH_CHANGE, this._monthChangeListener);
        this._open = true;
        this._adapter.setHostAttribute(BASE_DATE_PICKER_CONSTANTS.observedAttributes.OPEN);
        if (emitOpenEvent) {
            this._emitOpenEvent();
        }
    }
    _closeCalendar(emitCloseEvent = false) {
        this._open = false;
        this._adapter.removeHostAttribute(BASE_DATE_PICKER_CONSTANTS.observedAttributes.OPEN);
        this._adapter.removeCalendarListener(CALENDAR_CONSTANTS.events.DATE_SELECT, this._dateSelectListener);
        this._adapter.removeCalendarListener(CALENDAR_CONSTANTS.events.MONTH_CHANGE, this._monthChangeListener);
        this._adapter.detachCalendar();
        if (emitCloseEvent) {
            this._emitCloseEvent();
        }
    }
    _onInputKeydown(evt) {
        // Handle keyboard shortcuts that make use of the shift key first
        if (evt.shiftKey) {
            switch (evt.key) {
                case 'Delete':
                case 'Backspace':
                    evt.preventDefault();
                    this._onClear();
                    if (this._open) {
                        this._closeCalendar(true);
                    }
                    return;
                case 'ArrowLeft':
                case 'ArrowRight':
                case 'm':
                case 'M':
                case 'y':
                case 'Y':
                case 'd':
                case 'D':
                    if (this._open) {
                        evt.preventDefault();
                        this._adapter.propagateCalendarKey(evt);
                        return;
                    }
                    break;
            }
        }
        switch (evt.key) {
            case 'Escape':
                if (this._open) {
                    evt.preventDefault();
                    evt.stopPropagation();
                    this._closeCalendar(true);
                }
                break;
            case 'ArrowDown':
                evt.preventDefault();
                if (!this._open) {
                    this._adapter.selectInputText();
                    this._openCalendar(true);
                }
                else {
                    this._adapter.propagateCalendarKey(evt);
                }
                break;
            case 'ArrowUp':
                if (this._open) {
                    evt.preventDefault();
                    this._adapter.propagateCalendarKey(evt);
                }
                break;
            case 'ArrowLeft':
            case 'ArrowRight':
            case 'Enter':
            case 'Home':
            case 'End':
                if (this._open) {
                    if (evt.key === 'Enter') {
                        evt.stopPropagation();
                    }
                    evt.preventDefault();
                    // If we have an input mask applied, we want to make sure it doesn't receive the key events for left and right keys
                    const isLeftRight = ['ArrowLeft', 'ArrowRight'].includes(evt.key);
                    if (this._masked && isLeftRight) {
                        evt.stopImmediatePropagation();
                    }
                    this._adapter.propagateCalendarKey(evt);
                }
                break;
            case 'n':
            case 't':
                evt.preventDefault();
                this._onToday();
                if (this._open) {
                    this._closeCalendar(true);
                }
                break;
            case 'PageUp':
            case 'PageDown':
                if (this._open) {
                    evt.preventDefault();
                    this._adapter.propagateCalendarKey(evt);
                }
                break;
            case 'Tab':
                if (this._open) {
                    const activeDate = this._adapter.getCalendarActiveDate();
                    if (activeDate) {
                        this._onDateSelected({
                            date: activeDate,
                            selected: true,
                            type: 'date'
                        });
                    }
                }
                break;
        }
    }
    _getSanitizedDateString(value) {
        return value.replace(/_|[a-z]|[A-Z]/g, '').replace(/\/$|\/\/$/, '');
    }
    _onToggleMousedown(evt) {
        if (this._disabled) {
            return;
        }
        evt.stopPropagation();
        if (this._adapter.isInputDisabled()) {
            return;
        }
        evt.preventDefault();
        if (this._open) {
            this._closeCalendar(true);
        }
        else {
            if (!Platform.isMobile) {
                this._adapter.tryFocusInput();
            }
            else {
                this._adapter.tryBlurInput();
            }
            this._openCalendar(true);
        }
    }
    _onDropdownClose() {
        this._closeCalendar(true);
    }
    _onMonthChanged(event) {
        this._adapter.redispatchEvent(event);
    }
    _onActiveDescendantChanged(id) {
        this._adapter.setActiveDescendant(id);
    }
    _formatInputValue() {
        const inputValue = this._adapter.getInputValue();
        if (inputValue) {
            const parsedDate = this._parseDateString(inputValue);
            if (isValidDate(parsedDate) && this._isDateValueAcceptable(parsedDate)) {
                const formattedDate = this._formatDate(parsedDate);
                if (formattedDate && formattedDate !== inputValue) {
                    this._adapter.setInputValue(formattedDate, this._notifyInputValueChanges);
                }
            }
            else {
                if (!this._allowInvalidDate) {
                    this._adapter.setInputValue('', this._notifyInputValueChanges);
                }
            }
        }
    }
    _formatDate(date) {
        if (!isValidDate(date)) {
            return '';
        }
        return typeof this._formatCallback === 'function' ? this._formatCallback(date) : formatDate(date);
    }
    _parseDateString(value) {
        value = value.replace(/_|\s/g, '');
        if (!value || !value.length) {
            return null;
        }
        const parsedDate = typeof this._parseCallback === 'function' ? this._parseCallback(value) : parseDateString(value);
        return isValidDate(parsedDate) ? parsedDate : null;
    }
    _coerceDateValue(value) {
        if (typeof value === 'string') {
            return this._parseDateString(value);
        }
        return value;
    }
    _getTypedValue(value) {
        switch (this._valueMode) {
            case 'object':
                return value;
            case 'string':
                return this._formatDate(value);
            case 'iso-string':
                if (value) {
                    // Remove the time portion of the date
                    value.setHours(0, 0, 0, 0);
                }
                return value ? value.toISOString() : null;
            default:
                return value;
        }
    }
    _isDateValueAcceptable(value) {
        if (!value || this._allowInvalidDate) {
            return true;
        }
        const passesMinDate = () => this._min && !this._allowInvalidDate ? this._min.getTime() <= value.getTime() : true;
        const passesMaxDate = () => this._max && !this._allowInvalidDate ? this._max.getTime() >= value.getTime() : true;
        const disabledDates = () => Array.isArray(this._disabledDates) ? this._disabledDates : this._disabledDates ? [this._disabledDates] : [];
        const isNotDisabled = () => !disabledDates().some(bd => bd.getTime() === value.getTime());
        const dayNotDisabled = () => !(this._disabledDaysOfWeek && this._disabledDaysOfWeek.includes(value.getDay()));
        const dateNotDisabledThroughCallback = () => typeof this._disableDayCallback !== 'function' || !this._disableDayCallback(value);
        return passesMinDate() && passesMaxDate() && isNotDisabled() && dayNotDisabled() && dateNotDisabledThroughCallback();
    }
    _initializeMask() {
        const options = {
            showMaskFormat: this._showMaskFormat && this._adapter.isInputFocused(),
            pattern: this._maskFormat,
            onChange: (value) => this._handleInput(value)
        };
        if (this._prepareMaskCallback) {
            options.prepareCallback = (value, masked, flags, maskInstance) => {
                return this._prepareMaskCallback.call(null, value, masked, flags, maskInstance);
            };
        }
        this._adapter.initializeMask(options);
    }
    _applyMask() {
        if (this._masked) {
            this._initializeMask();
        }
        else {
            this._adapter.destroyMask();
            this._formatInputValue();
        }
    }
    _applyMin() {
        if (this._isInitialized) {
            this._adapter.setCalendarMinDate(this._min);
        }
    }
    _applyMax() {
        if (this._isInitialized) {
            this._adapter.setCalendarMaxDate(this._max);
        }
    }
    _applyDisableDayCallback() {
        this._adapter.setCalendarDisableDayCallback(this._disableDayCallback);
    }
    _applyDisabled() {
        this._adapter.setDisabled(this._disabled);
    }
    get valueMode() {
        return this._valueMode;
    }
    set valueMode(value) {
        if (this._valueMode !== value) {
            const isValidMode = ['object', 'string', 'iso-string'].includes(value);
            if (!isValidMode) {
                return;
            }
            this._valueMode = value;
            this._emitChangeEvent(this._value);
            this._adapter.setHostAttribute(BASE_DATE_PICKER_CONSTANTS.observedAttributes.VALUE_MODE, this._valueMode);
        }
    }
    get min() {
        return this._min ? new Date(this._min.getTime()) : null;
    }
    set min(value) {
        if (this._min !== value) {
            const date = this._coerceDateValue(value);
            this._min = !!date ? new Date(date.getTime()) : null;
            this._applyMin();
            if (this._isInitialized && this._open) {
                this._adapter.setCalendarMinDate(this._min);
            }
        }
    }
    get max() {
        return this._max ? new Date(this._max.getTime()) : null;
    }
    set max(value) {
        if (this._max !== value) {
            this._max = this._coerceDateValue(value);
            this._applyMax();
            if (this._isInitialized && this._open) {
                this._adapter.setCalendarMaxDate(this._max);
            }
        }
    }
    get open() {
        return this._open;
    }
    set open(value) {
        if (this._open !== value) {
            if (this._isInitialized) {
                this._open = value;
                if (this._open) {
                    this._openCalendar();
                }
                else {
                    this._closeCalendar();
                }
            }
        }
    }
    get disabled() {
        return this._disabled;
    }
    set disabled(value) {
        if (this._disabled !== value) {
            this._disabled = value;
            if (this._isInitialized) {
                this._applyDisabled();
            }
        }
    }
    get disabledDates() {
        if (!this._disabledDates) {
            return null;
        }
        if (Array.isArray(this._disabledDates)) {
            return this._disabledDates.map(d => new Date(d.getTime()));
        }
        return new Date(this._disabledDates.getTime());
    }
    set disabledDates(value) {
        if (!value) {
            this._disabledDates = null;
        }
        else if (Array.isArray(value)) {
            this._disabledDates = value.map(d => new Date(d.getTime()));
        }
        else {
            this._disabledDates = new Date(value.getTime());
        }
        if (this._isInitialized && this._open) {
            this._adapter.setCalendarDisabledDates(this._disabledDates);
        }
        this._applyDisabledDates();
    }
    get popupClasses() {
        if (Array.isArray(this._popupClasses)) {
            return [...this._popupClasses];
        }
        return this._popupClasses;
    }
    set popupClasses(value) {
        if (Array.isArray(value)) {
            this._popupClasses = [...value];
        }
        else {
            this._popupClasses = value;
        }
    }
    get masked() {
        return this._masked;
    }
    set masked(value) {
        if (this._masked !== value) {
            this._masked = value;
            if (this._isInitialized) {
                if (this._masked) {
                    this._removeInputChangeListeners();
                    this._applyMask();
                }
                else {
                    this._adapter.destroyMask();
                    this._setInputChangeListeners();
                    this._formatInputValue();
                }
            }
        }
    }
    get maskFormat() {
        return this._maskFormat;
    }
    set maskFormat(value) {
        if (this._maskFormat !== value) {
            this._maskFormat = value;
            if (this._isInitialized) {
                this._applyMask();
            }
        }
    }
    get showMaskFormat() {
        return this._showMaskFormat;
    }
    set showMaskFormat(value) {
        if (this._showMaskFormat !== value) {
            this._showMaskFormat = value;
        }
    }
    get notifyInputValueChanges() {
        return this._notifyInputValueChanges;
    }
    set notifyInputValueChanges(value) {
        this._notifyInputValueChanges = value;
    }
    get allowInvalidDate() {
        return this._allowInvalidDate;
    }
    set allowInvalidDate(value) {
        if (this._allowInvalidDate !== value) {
            this._allowInvalidDate = value;
            this._adapter.toggleHostAttribute(BASE_DATE_PICKER_CONSTANTS.observedAttributes.ALLOW_INVALID_DATE, this._allowInvalidDate);
        }
    }
    get disabledDaysOfWeek() {
        return this._disabledDaysOfWeek;
    }
    set disabledDaysOfWeek(value) {
        if (this._disabledDaysOfWeek !== value) {
            this._disabledDaysOfWeek = value;
            if (this._isInitialized && this._open) {
                this._adapter.setCalendarDisabledDaysOfWeek(this._disabledDaysOfWeek);
            }
            this._applyDisabledDaysOfWeek();
        }
    }
    get disableDayCallback() {
        return this._disableDayCallback;
    }
    set disableDayCallback(value) {
        if (value !== this._disableDayCallback) {
            this._disableDayCallback = value;
            this._applyDisableDayCallback();
        }
    }
    get parseCallback() {
        return this._parseCallback;
    }
    set parseCallback(cb) {
        this._parseCallback = cb;
        if (this._isInitialized) {
            this._applyMask();
        }
    }
    get formatCallback() {
        return this._formatCallback;
    }
    set formatCallback(cb) {
        this._formatCallback = cb;
        if (this._isInitialized) {
            this._applyMask();
        }
    }
    get prepareMaskCallback() {
        return this._prepareMaskCallback;
    }
    set prepareMaskCallback(cb) {
        this._prepareMaskCallback = cb;
        if (this._isInitialized) {
            this._applyMask();
        }
    }
    get showToday() {
        return this._showToday;
    }
    set showToday(value) {
        this._showToday = value;
    }
    get showClear() {
        return this._showClear;
    }
    set showClear(value) {
        this._showClear = value;
    }
    get yearRange() {
        return this._yearRange;
    }
    set yearRange(value) {
        if (this._yearRange !== value) {
            this._yearRange = value;
            if (this._isInitialized && this._open) {
                this._adapter.setCalendarYearRange(this._yearRange);
            }
        }
    }
    get locale() {
        return this._locale;
    }
    set locale(value) {
        if (this._locale !== value) {
            this._locale = value;
            if (this._isInitialized && this._open) {
                this._adapter.setCalendarLocale(this._locale);
            }
        }
    }
}
