import { DateTime } from "./datetime";
import { dummy } from "./helpers";
export class EasePicker {
    constructor(options) {
        this.Calendar = new Calendar(this);
        this.calendars = [];
        this.datePicked = [];
        this.cssLoaded = 0;
        this.binds = {
            hidePicker: this.hidePicker.bind(this),
            show: this.show.bind(this),
            setStartDate: this.setStartDate.bind(this),
            setEndDate: this.setEndDate.bind(this),
            setDateRange: this.setDateRange.bind(this),
            getStartDate: this.getStartDate.bind(this),
            getEndDate: this.getEndDate.bind(this),
            onViewRanged: this.onViewRanged.bind(this),
            onViewLocked: this.onViewLocked.bind(this),
            onShow: this.onShow.bind(this),
            onMouseEnter: this.onMouseEnter.bind(this),
            onMouseLeave: this.onMouseLeave.bind(this),
            onClickCalendarDay: this.onClickCalendarDay.bind(this),
            onClickApplyButton: this.onClickApplyButton.bind(this),
            parseValues: this.parseValues.bind(this),
            updateValues: this.updateValues.bind(this),
            clear: this.clear.bind(this),
        };
        this.options = {
            doc: document,
            css: [],
            element: null,
            firstDay: 1,
            grid: 1,
            calendars: 1,
            lang: 'en-US',
            date: null,
            format: 'YYYY-MM-DD',
            readonly: true,
            autoApply: true,
            shadowDom: true,
            ranged: false,
            locked: false,
            header: false,
            inline: false,
            scrollToDate: true,
            locale: {
                nextMonth: '<svg width="11" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M2.748 16L0 13.333 5.333 8 0 2.667 2.748 0l7.919 8z" fill-rule="nonzero"/></svg>',
                previousMonth: '<svg width="11" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M7.919 0l2.748 2.667L5.333 8l5.334 5.333L7.919 16 0 8z" fill-rule="nonzero"/></svg>',
                cancel: 'Cancel',
                apply: 'Apply',
                zero: '',
                one: 'day',
                two: '',
                few: '',
                many: '',
                other: 'days',
            },
            documentClick: this.binds.hidePicker,
            elementEnd: null,
            startDate: null,
            endDate: null,
            repick: false,
            strict: true,
            delimiter: ' - ',
            tooltip: true,
            tooltipNumber: (num) => {
                return num;
            },
            minDate: null,
            maxDate: null,
            minDays: null,
            maxDays: null,
            selectForward: null,
            selectBackward: null,
            presets: true,
            inseparable: false,
            filter: null,
        };
        this.ui = {
            container: null,
            shadowRoot: null,
            wrapper: null,
        };
        const locales = Object.assign(Object.assign({}, this.options.locale), options.locale);
        this.options = Object.assign(Object.assign({}, this.options), options);
        this.options.locale = locales;
        this.handleOptions();
        this.ui.wrapper = document.createElement('span');
        this.ui.wrapper.style.display = 'none';
        this.ui.wrapper.style.position = 'absolute';
        this.ui.wrapper.style.pointerEvents = 'none';
        this.ui.wrapper.className = 'easepick-wrapper';
        if (this.options.shadowDom) {
            this.ui.wrapper.attachShadow({ mode: 'open' });
            this.ui.shadowRoot = this.ui.wrapper.shadowRoot;
        }
        this.ui.container = document.createElement('div');
        this.ui.container.className = 'easepick-container';
        if (this.options.zIndex) {
            this.ui.container.style.zIndex = String(this.options.zIndex);
        }
        if (this.options.inline) {
            this.ui.wrapper.style.position = 'relative';
            this.ui.container.classList.add('inline');
        }
        (this.ui.shadowRoot || this.ui.wrapper).appendChild(this.ui.container);
        this.options.element.after(this.ui.wrapper);
        this.handleCSS();
        this.options.element.addEventListener('click', this.binds.show);
        this.on('view', this.onView.bind(this));
        this.on('render', this.onRender.bind(this));
        this.options.locked && this.attachLocked();
        this.options.ranged && this.attachRanged();
        this.parseValues();
        if (typeof this.options.setup === 'function') {
            this.options.setup(this);
        }
        this.on('click', this.onClick.bind(this));
        const targetDate = this.options.scrollToDate ? this.getDate() : null;
        this.renderAll(targetDate);
    }
    /**
     * Attach Ranged capabilities
     */
    attachRanged() {
        this.ui.container.classList.add('range-plugin');
        if (this.options.elementEnd) {
            if (!(this.options.elementEnd instanceof HTMLElement)) {
                this.options.elementEnd = this.options.doc.querySelector(this.options.elementEnd);
            }
            if (this.options.elementEnd instanceof HTMLInputElement) {
                this.options.elementEnd.readOnly = this.options.readonly;
            }
            if (typeof this.options.documentClick === 'function') {
                document.removeEventListener('click', this.options.documentClick, true);
                if (typeof this.options.documentClick === 'function') {
                    document.addEventListener('click', this.options.documentClick, true);
                }
            }
            this.options.elementEnd.addEventListener('click', this.show.bind(this));
        }
        this.options.repick = this.options.repick && this.options.elementEnd instanceof HTMLElement;
        this.options.date = null;
        this.on('view', this.onViewRanged.bind(this));
        this.on('show', this.binds.onShow);
        this.on('mouseenter', this.binds.onMouseEnter, true);
        this.on('mouseleave', this.binds.onMouseLeave, true);
        this.checkIntlPluralLocales();
    }
    /**
     * Attach Locked capabilities
     */
    attachLocked() {
        this.ui.container.classList.add('lock-plugin');
        if (this.options.minDate) {
            this.options.minDate = new DateTime(this.options.minDate, this.options.format, this.options.lang);
        }
        if (this.options.maxDate) {
            this.options.maxDate = new DateTime(this.options.maxDate, this.options.format, this.options.lang);
            if (this.options.maxDate instanceof DateTime
                && this.options.calendars > 1
                && this.calendars[0].isSame(this.options.maxDate, 'month')) {
                const d = this.calendars[0].clone().subtract(1, 'month');
                this.gotoDate(d);
            }
        }
        this.on('view', this.onViewLocked.bind(this));
    }
    /**
     * Add listener to container element
     *
     * @param type
     * @param listener
     * @param options
     */
    on(type, listener, options = {}) {
        this.ui.container.addEventListener(type, listener, options);
    }
    /**
     * Remove listener from container element
     *
     * @param type
     * @param listener
     * @param options
     */
    off(type, listener, options = {}) {
        this.ui.container.removeEventListener(type, listener, options);
    }
    /**
     * Dispatch an event
     *
     * @param type
     * @param detail
     * @returns
     */
    trigger(type, detail = {}) {
        return this.ui.container.dispatchEvent(new CustomEvent(type, { detail }));
    }
    /**
     * Destroy picker
     */
    destroy() {
        this.options.element.removeEventListener('click', this.binds.show);
        if (typeof this.options.documentClick === 'function') {
            document.removeEventListener('click', this.options.documentClick, true);
        }
        if (this.options.ranged) {
            this.off('view', this.binds.onViewRanged);
            this.off('show', this.binds.onShow);
            this.off('mouseenter', this.binds.onMouseEnter, true);
            this.off('mouseleave', this.binds.onMouseLeave, true);
        }
        if (this.options.locked) {
            this.off('view', this.onViewLocked);
        }
        this.ui.wrapper.remove();
    }
    /**
     * Fired on render event
     *
     * @param event
     */
    onRender(event) {
        const { view, date } = event.detail;
        this.Calendar.render(date, view);
    }
    onView(event) {
        const { view, target } = event.detail;
        if (view === 'Footer' && this.datePicked.length) {
            const applyButton = target.querySelector('.apply-button');
            applyButton.disabled = false;
        }
    }
    /**
     *
     * @param element
     */
    onClickHeaderButton(element) {
        if (this.isCalendarHeaderButton(element)) {
            if (element.classList.contains('next-button')) {
                this.calendars[0].add(1, 'month');
            }
            else {
                this.calendars[0].subtract(1, 'month');
            }
            this.renderAll(this.calendars[0]);
        }
    }
    /**
     *
     * @param element
     */
    onClickCalendarDay(element) {
        if (this.options.ranged) {
            return this.onClickCalendarDayRanged(element);
        }
        if (this.isCalendarDay(element)) {
            const date = new DateTime(element.dataset.time);
            if (this.options.autoApply) {
                this.setDate(date);
                this.trigger('select', { date: this.getDate() });
                this.hide();
            }
            else {
                this.datePicked[0] = date;
                this.trigger('preselect', { date: this.getDate() });
                this.renderAll();
            }
        }
    }
    /**
     *
     * @param element
     */
    onClickApplyButton(element) {
        if (this.options.ranged) {
            return this.onClickApplyButtonRanged(element);
        }
        if (this.isApplyButton(element)) {
            if (this.datePicked[0] instanceof Date) {
                const date = this.datePicked[0].clone();
                this.setDate(date);
            }
            this.hide();
            this.trigger('select', { date: this.getDate() });
        }
    }
    /**
     *
     * @param element
     * @returns
     */
    onClickCancelButton(element) {
        if (this.isCancelButton(element)) {
            this.hide();
        }
    }
    /**
     * Fired on click event
     *
     * @param event
     */
    onClick(event) {
        const target = event.target;
        if (target instanceof HTMLElement) {
            const element = target.closest('.unit');
            if (!(element instanceof HTMLElement))
                return;
            this.onClickHeaderButton(element);
            this.onClickCalendarDay(element);
            this.onClickApplyButton(element);
            this.onClickCancelButton(element);
        }
    }
    /**
     * Determine if the picker is visible or not
     *
     * @returns Boolean
     */
    isShown() {
        return this.ui.container.classList.contains('inline')
            || this.ui.container.classList.contains('show');
    }
    /**
     * Show the picker
     *
     * @param event
     */
    show(event) {
        if (this.isShown())
            return;
        const target = event && 'target' in event ? event.target : this.options.element;
        const { top, left } = this.adjustPosition(target);
        this.ui.container.style.top = `${top}px`;
        this.ui.container.style.left = `${left}px`;
        this.ui.container.classList.add('show');
        this.trigger('show', { target: target });
    }
    /**
     * Hide the picker
     */
    hide() {
        this.ui.container.classList.remove('show');
        this.datePicked.length = 0;
        this.renderAll();
        this.trigger('hide');
    }
    /**
     * Set date programmatically
     *
     * @param date
     */
    setDate(date) {
        const d = new DateTime(date, this.options.format);
        this.options.date = d.clone();
        this.updateValues();
        if (this.calendars.length) {
            this.renderAll();
        }
    }
    /**
     *
     * @returns DateTime
     */
    getDate() {
        return this.options.date instanceof DateTime
            ? this.options.date.clone()
            : null;
    }
    /**
     * Parse `date` option or value of input element
     */
    parseValues() {
        if (this.options.ranged) {
            return this.parseValuesRanged();
        }
        if (this.options.date) {
            this.setDate(this.options.date);
        }
        else if (this.options.element instanceof HTMLInputElement && this.options.element.value.length) {
            this.setDate(this.options.element.value);
        }
        if (!(this.options.date instanceof Date)) {
            this.options.date = null;
        }
    }
    /**
     * Update value of input element
     */
    updateValues() {
        if (this.options.ranged) {
            return this.updateValuesRanged();
        }
        const date = this.getDate();
        const formatString = date instanceof Date ? date.format(this.options.format, this.options.lang) : '';
        const el = this.options.element;
        if (el instanceof HTMLInputElement) {
            el.value = formatString;
        }
        else if (el instanceof HTMLElement) {
            el.innerText = formatString;
        }
    }
    /**
     * Function for documentClick option
     * Allows the picker to close when the user clicks outside
     *
     * @param e
     */
    hidePicker(e) {
        if (this.options.ranged) {
            return this.hidePickerRanged(e);
        }
        let target = e.target;
        let host = null;
        if (target.shadowRoot) {
            target = e.composedPath()[0];
            host = target.getRootNode().host;
        }
        if (this.isShown()
            && this.options.inline === false
            && host !== this.ui.wrapper
            && target !== this.options.element) {
            this.hide();
        }
    }
    /**
     * Render entire picker layout
     *
     * @param date
     */
    renderAll(date) {
        this.trigger('render', { view: 'Container', date: (date || this.calendars[0]).clone() });
    }
    /**
     * Determines if the element is buttons of header (previous month, next month)
     *
     * @param element
     * @returns Boolean
     */
    isCalendarHeaderButton(element) {
        return ['previous-button', 'next-button'].some(x => element.classList.contains(x));
    }
    /**
     * Determines if the element is day element
     *
     * @param element
     * @returns Boolean
     */
    isCalendarDay(element) {
        return element.classList.contains('day');
    }
    /**
     * Determines if the element is the apply button
     *
     * @param element
     * @returns Boolean
     */
    isApplyButton(element) {
        return element.classList.contains('apply-button');
    }
    /**
     * Determines if the element is the cancel button
     *
     * @param element
     * @returns Boolean
     */
    isCancelButton(element) {
        return element.classList.contains('cancel-button');
    }
    /**
     * Change visible month
     *
     * @param date
     */
    gotoDate(date) {
        const toDate = new DateTime(date, this.options.format);
        toDate.setDate(1);
        this.calendars[0] = toDate.clone();
        this.renderAll();
    }
    /**
     * Clear date selection
     */
    clear() {
        if (this.options.ranged) {
            return this.clearRanged();
        }
        this.options.date = null;
        this.datePicked.length = 0;
        this.updateValues();
        this.renderAll();
        this.trigger('clear');
    }
    /**
     * Handling parameters passed by the user
     */
    handleOptions() {
        if (!(this.options.element instanceof HTMLElement)) {
            this.options.element = this.options.doc.querySelector(this.options.element);
        }
        if (typeof this.options.documentClick === 'function') {
            document.addEventListener('click', this.options.documentClick, true);
        }
        if (this.options.element instanceof HTMLInputElement) {
            this.options.element.readOnly = this.options.readonly;
        }
        if (this.options.date) {
            this.calendars[0] = new DateTime(this.options.date, this.options.format);
        }
        else {
            this.calendars[0] = new DateTime();
        }
    }
    /**
     * Apply CSS passed by the user
     */
    handleCSS() {
        if (Array.isArray(this.options.css)) {
            this.options.css.forEach(cssLink => {
                const link = document.createElement('link');
                link.href = cssLink;
                link.rel = 'stylesheet';
                const onReady = () => {
                    this.cssLoaded++;
                    if (this.cssLoaded === this.options.css.length) {
                        this.ui.wrapper.style.display = '';
                    }
                };
                link.addEventListener('load', onReady);
                link.addEventListener('error', onReady);
                (this.ui.shadowRoot || document.body).append(link);
            });
            if (!this.options.shadowDom && !this.options.css.length) {
                this.ui.wrapper.style.display = '';
            }
        }
        else if (typeof this.options.css === 'string') {
            const style = document.createElement('style');
            const styleText = document.createTextNode(this.options.css);
            style.appendChild(styleText);
            (this.ui.shadowRoot || document.body).append(style);
            this.ui.wrapper.style.display = '';
        }
        else if (typeof this.options.css === 'function') {
            this.options.css.call(this, this);
            this.ui.wrapper.style.display = '';
        }
    }
    /**
     * Calculate the position of the picker
     *
     * @param element
     * @returns { top, left }
     */
    adjustPosition(element) {
        const rect = element.getBoundingClientRect();
        const wrapper = this.ui.wrapper.getBoundingClientRect();
        this.ui.container.classList.add('calc');
        const container = this.ui.container.getBoundingClientRect();
        this.ui.container.classList.remove('calc');
        let top = rect.bottom - wrapper.bottom;
        let left = rect.left - wrapper.left;
        if (typeof window !== 'undefined') {
            if (window.innerHeight < top + container.height
                && top - container.height >= 0) {
                top = rect.top - wrapper.top - container.height;
            }
            if (window.innerWidth < left + container.width
                && rect.right - container.width >= 0) {
                left = rect.right - wrapper.right - container.width;
            }
        }
        return {
            left,
            top,
        };
    }
    /**
   * Parse `startDate`, `endDate` options or value of input elements
   */
    parseValuesRanged() {
        if (this.options.startDate || this.options.endDate) {
            if (this.options.strict) {
                if (this.options.startDate && this.options.endDate) {
                    this.setDateRange(this.options.startDate, this.options.endDate);
                }
                else {
                    this.options.startDate = null;
                    this.options.endDate = null;
                }
            }
            else {
                if (this.options.startDate) {
                    this.setStartDate(this.options.startDate);
                }
                if (this.options.endDate) {
                    this.setEndDate(this.options.endDate);
                }
            }
            return;
        }
        if (this.options.elementEnd) {
            if (this.options.strict) {
                if (this.options.element instanceof HTMLInputElement
                    && this.options.element.value.length
                    && this.options.elementEnd instanceof HTMLInputElement
                    && this.options.elementEnd.value.length) {
                    this.setDateRange(this.options.element.value, this.options.elementEnd.value);
                }
            }
            else {
                if (this.options.element instanceof HTMLInputElement
                    && this.options.element.value.length) {
                    this.setStartDate(this.options.element.value);
                }
                if (this.options.elementEnd instanceof HTMLInputElement
                    && this.options.elementEnd.value.length) {
                    this.setEndDate(this.options.elementEnd.value);
                }
            }
        }
        else if (this.options.element instanceof HTMLInputElement && this.options.element.value.length) {
            const [_start, _end] = this.options.element.value.split(this.options.delimiter);
            if (this.options.strict) {
                if (_start && _end) {
                    this.setDateRange(_start, _end);
                }
            }
            else {
                if (_start)
                    this.setStartDate(_start);
                if (_end)
                    this.setEndDate(_end);
            }
        }
    }
    /**
     * Update value of input element
     */
    updateValuesRanged() {
        const el = this.options.element;
        const elEnd = this.options.elementEnd;
        const start = this.getStartDate();
        const end = this.getEndDate();
        const startString = start instanceof Date
            ? start.format(this.options.format, this.options.lang)
            : '';
        const endString = end instanceof Date
            ? end.format(this.options.format, this.options.lang)
            : '';
        if (elEnd) {
            if (el instanceof HTMLInputElement) {
                el.value = startString;
            }
            else if (el instanceof HTMLElement) {
                el.innerText = startString;
            }
            if (elEnd instanceof HTMLInputElement) {
                elEnd.value = endString;
            }
            else if (elEnd instanceof HTMLElement) {
                elEnd.innerText = endString;
            }
        }
        else {
            const delimiter = startString || endString ? this.options.delimiter : '';
            const formatString = `${startString}${delimiter}${endString}`;
            if (el instanceof HTMLInputElement) {
                el.value = formatString;
            }
            else if (el instanceof HTMLElement) {
                el.innerText = formatString;
            }
        }
    }
    /**
     * Clear selection
     */
    clearRanged() {
        this.options.startDate = null;
        this.options.endDate = null;
        this.datePicked.length = 0;
        this.updateValues();
        this.renderAll();
        this.trigger('clear');
    }
    /**
     * Function `show` event
     *
     * @param event
     */
    onShow(event) {
        const { target } = event.detail;
        this.triggerElement = target;
        if (this.options.scrollToDate && this.getStartDate() instanceof Date) {
            this.gotoDate(this.getStartDate());
        }
        this.initializeRepick();
    }
    /**
     * Function `view` event
     * Adds HTML layout of current plugin to the picker layout
     *
     * @param event
     */
    onViewRanged(event) {
        const { view, target } = event.detail;
        if (view === 'Main') {
            this.tooltipElement = document.createElement('span');
            this.tooltipElement.className = 'range-plugin-tooltip';
            target.appendChild(this.tooltipElement);
        }
        if (view === 'CalendarDay') {
            const date = new DateTime(target.dataset.time);
            const datePicked = this.datePicked;
            const start = datePicked.length ? this.datePicked[0] : this.getStartDate();
            const end = datePicked.length ? this.datePicked[1] : this.getEndDate();
            if (start === null || start === void 0 ? void 0 : start.isSame(date, 'day')) {
                target.classList.add('start');
            }
            if (start && end) {
                if (end.isSame(date, 'day')) {
                    target.classList.add('end');
                }
                if (date.isBetween(start, end)) {
                    target.classList.add('in-range');
                }
            }
        }
        if (view === 'Footer') {
            const allowApplyBtn = (this.datePicked.length === 1 && !this.options.strict)
                || this.datePicked.length === 2;
            const applyButton = target.querySelector('.apply-button');
            applyButton.disabled = !allowApplyBtn;
        }
    }
    /**
     * Function for documentClick option
     * Allows the picker to close when the user clicks outside
     *
     * @param e
     */
    hidePickerRanged(e) {
        let target = e.target;
        let host = null;
        if (this.ui.shadowRoot && target.shadowRoot) {
            target = e.composedPath()[0];
            host = target.getRootNode().host;
        }
        if (!this.ui.shadowRoot && target.closest('.easepick-wrapper')) {
            host = target.closest('.easepick-wrapper');
        }
        if (this.isShown()
            && host !== this.ui.wrapper
            && target !== this.options.element
            && target !== this.options.elementEnd) {
            this.hide();
        }
    }
    /**
     * Set startDate programmatically
     *
     * @param date
     */
    setStartDate(date) {
        const d = new DateTime(date, this.options.format);
        this.options.startDate = d ? d.clone() : null;
        this.updateValues();
        this.renderAll();
    }
    /**
     * Set endDate programmatically
     *
     * @param date
     */
    setEndDate(date) {
        const d = new DateTime(date, this.options.format);
        this.options.endDate = d ? d.clone() : null;
        this.updateValues();
        this.renderAll();
    }
    /**
     * Set date range programmatically
     *
     * @param start
     * @param end
     */
    setDateRange(start, end) {
        const startDate = new DateTime(start, this.options.format);
        const endDate = new DateTime(end, this.options.format);
        this.options.startDate = startDate ? startDate.clone() : null;
        this.options.endDate = endDate ? endDate.clone() : null;
        this.updateValues();
        this.renderAll();
    }
    /**
     *
     * @returns DateTime
     */
    getStartDate() {
        return this.options.startDate instanceof Date ? this.options.startDate.clone() : null;
    }
    /**
     *
     * @returns
     */
    getEndDate() {
        return this.options.endDate instanceof Date ? this.options.endDate.clone() : null;
    }
    /**
     * Handle `mouseenter` event
     *
     * @param event
     */
    onMouseEnter(event) {
        const target = event.target;
        if (target instanceof HTMLElement) {
            if (this.isContainer(target)) {
                this.initializeRepick();
            }
            const element = target.closest('.unit');
            if (!(element instanceof HTMLElement))
                return;
            if (this.isCalendarDay(element)) {
                if (this.datePicked.length !== 1)
                    return;
                let date1 = this.datePicked[0].clone();
                let date2 = new DateTime(element.dataset.time);
                let isFlipped = false;
                if (date1.isAfter(date2, 'day')) {
                    const tempDate = date1.clone();
                    date1 = date2.clone();
                    date2 = tempDate.clone();
                    isFlipped = true;
                }
                const days = Array.from(this.ui.container.querySelectorAll('.day'));
                days.forEach((d) => {
                    const date = new DateTime(d.dataset.time);
                    const dayView = this.Calendar.getCalendarDayView(date);
                    if (date.isBetween(date1, date2)) {
                        dayView.classList.add('in-range');
                    }
                    if (date.isSame(this.datePicked[0], 'day')) {
                        dayView.classList.add('start');
                        dayView.classList.toggle('flipped', isFlipped);
                    }
                    if (d === element) {
                        dayView.classList.add('end');
                        dayView.classList.toggle('flipped', isFlipped);
                    }
                    d.className = dayView.className;
                });
                if (this.options.tooltip) {
                    const diff = this.options.tooltipNumber(date2.diff(date1, 'day') + 1);
                    if (diff > 0) {
                        const pluralKey = new Intl.PluralRules(this.options.lang).select(diff);
                        const text = `${diff} ${this.options.locale[pluralKey]}`;
                        this.showTooltip(element, text);
                    }
                    else {
                        this.hideTooltip();
                    }
                }
            }
        }
    }
    /**
     * Handle `mouseleave` event
     *
     * @param event
     */
    onMouseLeave(event) {
        if (this.isContainer(event.target) && this.options.repick) {
            const start = this.getStartDate();
            const end = this.getEndDate();
            if (start && end) {
                this.datePicked.length = 0;
                this.renderAll();
            }
        }
    }
    onClickCalendarDayRanged(element) {
        if (this.isCalendarDay(element)) {
            if (this.datePicked.length === 2) {
                this.datePicked.length = 0;
            }
            const date = new DateTime(element.dataset.time);
            this.datePicked[this.datePicked.length] = date;
            if (this.datePicked.length === 2 && this.datePicked[0].isAfter(this.datePicked[1])) {
                const tempDate = this.datePicked[1].clone();
                this.datePicked[1] = this.datePicked[0].clone();
                this.datePicked[0] = tempDate.clone();
            }
            if (this.datePicked.length === 1 || !this.options.autoApply) {
                this.trigger('preselect', {
                    start: this.datePicked[0] instanceof Date ? this.datePicked[0].clone() : null,
                    end: this.datePicked[1] instanceof Date ? this.datePicked[1].clone() : null,
                });
            }
            if (this.datePicked.length === 1) {
                if (!this.options.strict && this.options.autoApply) {
                    if (this.options.element === this.triggerElement) {
                        this.setStartDate(this.datePicked[0]);
                    }
                    if (this.options.elementEnd === this.triggerElement) {
                        this.setEndDate(this.datePicked[0]);
                    }
                    this.trigger('select', { start: this.getStartDate(), end: this.getEndDate() });
                }
                this.renderAll();
            }
            if (this.datePicked.length === 2) {
                if (this.options.autoApply) {
                    this.setDateRange(this.datePicked[0], this.datePicked[1]);
                    this.trigger('select', { start: this.getStartDate(), end: this.getEndDate() });
                    this.hide();
                }
                else {
                    this.hideTooltip();
                    this.renderAll();
                }
            }
        }
    }
    onClickApplyButtonRanged(element) {
        if (this.isApplyButton(element)) {
            if (this.datePicked.length === 1 && !this.options.strict) {
                if (this.options.element === this.triggerElement) {
                    this.options.endDate = null;
                    this.setStartDate(this.datePicked[0]);
                }
                if (this.options.elementEnd === this.triggerElement) {
                    this.options.startDate = null;
                    this.setEndDate(this.datePicked[0]);
                }
            }
            if (this.datePicked.length === 2) {
                this.setDateRange(this.datePicked[0], this.datePicked[1]);
            }
            this.trigger('select', { start: this.getStartDate(), end: this.getEndDate() });
            this.hide();
        }
    }
    /**
     * Displays tooltip of selected days
     *
     * @param element
     * @param text
     */
    showTooltip(element, text) {
        this.tooltipElement.style.visibility = 'visible';
        this.tooltipElement.innerHTML = text;
        const container = this.ui.container.getBoundingClientRect();
        const tooltip = this.tooltipElement.getBoundingClientRect();
        const day = element.getBoundingClientRect();
        let top = day.top;
        let left = day.left;
        top -= container.top;
        left -= container.left;
        top -= tooltip.height;
        left -= tooltip.width / 2;
        left += day.width / 2;
        this.tooltipElement.style.top = `${top}px`;
        this.tooltipElement.style.left = `${left}px`;
    }
    /**
     * Hide tooltip
     */
    hideTooltip() {
        this.tooltipElement.style.visibility = 'hidden';
    }
    /**
     * Determines if the locale option contains all required plurals
     */
    checkIntlPluralLocales() {
        if (!this.options.tooltip)
            return;
        const rules = [...new Set([
                new Intl.PluralRules(this.options.lang).select(0),
                new Intl.PluralRules(this.options.lang).select(1),
                new Intl.PluralRules(this.options.lang).select(2),
                new Intl.PluralRules(this.options.lang).select(6),
                new Intl.PluralRules(this.options.lang).select(18),
            ])];
        const locales = Object.keys(this.options.locale);
        if (!rules.every(x => locales.includes(x))) {
            console.warn(`Provide locales (${rules.join(', ')}) for correct tooltip text.`);
        }
    }
    /**
     * Handle `repick` option
     */
    initializeRepick() {
        if (!this.options.repick)
            return;
        const start = this.getStartDate();
        const end = this.getEndDate();
        if (end && this.triggerElement === this.options.element) {
            this.datePicked[0] = end;
        }
        if (start && this.triggerElement === this.options.elementEnd) {
            this.datePicked[0] = start;
        }
    }
    /**
     * Determines if the element is the picker container
     *
     * @param element
     * @returns Boolean
     */
    isContainer(element) {
        return element === this.ui.container;
    }
    /**
     * Function `view` event
     * Mark day elements as locked
     *
     * @param event
     */
    onViewLocked(event) {
        const { view, target, date } = event.detail;
        if (view === 'CalendarHeader') {
            if (this.options.minDate instanceof DateTime) {
                if (date.isSameOrBefore(this.options.minDate, 'month')) {
                    target.classList.add('no-previous-month');
                }
            }
            if (this.options.maxDate instanceof DateTime) {
                if (date.isSameOrAfter(this.options.maxDate, 'month')) {
                    target.classList.add('no-next-month');
                }
            }
        }
        if (view === 'CalendarDay') {
            const dateFrom = this.datePicked.length ? this.datePicked[0] : null;
            if (this.testFilter(date)) {
                target.classList.add('locked');
                return;
            }
            if (this.options.inseparable) {
                if (this.options.minDays) {
                    const date1 = date.clone().subtract(this.options.minDays - 1, 'day');
                    const date2 = date.clone().add(this.options.minDays - 1, 'day');
                    let lockedInPrevDays = false;
                    let lockedInNextDays = false;
                    while (date1.isBefore(date, 'day')) {
                        if (this.testFilter(date1)) {
                            lockedInPrevDays = true;
                            break;
                        }
                        date1.add(1, 'day');
                    }
                    while (date2.isAfter(date, 'day')) {
                        if (this.testFilter(date2)) {
                            lockedInNextDays = true;
                            break;
                        }
                        date2.subtract(1, 'day');
                    }
                    if (lockedInPrevDays && lockedInNextDays) {
                        target.classList.add('not-available');
                    }
                }
                if (this.rangeIsNotAvailable(date, dateFrom)) {
                    target.classList.add('not-available');
                }
            }
            if (this.dateIsNotAvailable(date, dateFrom)) {
                target.classList.add('not-available');
            }
        }
        if (this.options.presets && view === 'PresetPluginButton') {
            const startDate = new DateTime(Number(target.dataset.start));
            const endDate = new DateTime(Number(target.dataset.end));
            const diff = endDate.diff(startDate, 'day');
            const lessMinDays = this.options.minDays && diff < this.options.minDays;
            const moreMaxDays = this.options.maxDays && diff > this.options.maxDays;
            if (lessMinDays || moreMaxDays
                || this.lockMinDate(startDate)
                || this.lockMaxDate(startDate)
                || this.lockMinDate(endDate)
                || this.lockMaxDate(endDate)
                || this.rangeIsNotAvailable(startDate, endDate)) {
                target.setAttribute('disabled', 'disabled');
            }
        }
    }
    /**
     * Checks availability date
     *
     * @param date
     * @param start
     * @returns Boolean
     */
    dateIsNotAvailable(date, start) {
        return this.lockMinDate(date)
            || this.lockMaxDate(date)
            || this.lockMinDays(date, start)
            || this.lockMaxDays(date, start)
            || this.lockSelectForward(date)
            || this.lockSelectBackward(date);
    }
    /**
     * Checks the date range for availability
     *
     * @param date1
     * @param date2
     * @returns Boolean
     */
    rangeIsNotAvailable(date1, date2) {
        if (!date1 || !date2)
            return false;
        const start = (date1.isSameOrBefore(date2, 'day') ? date1 : date2).clone();
        const end = (date2.isSameOrAfter(date1, 'day') ? date2 : date1).clone();
        while (start.isSameOrBefore(end, 'day')) {
            if (this.testFilter(start)) {
                return true;
            }
            start.add(1, 'day');
        }
        return false;
    }
    /**
     * Handle `minDate` option
     *
     * @param date
     * @returns Boolean
     */
    lockMinDate(date) {
        return this.options.minDate instanceof DateTime
            ? date.isBefore(this.options.minDate, 'day')
            : false;
    }
    /**
     * Handle `maxDate` option
     *
     * @param date
     * @returns Boolean
     */
    lockMaxDate(date) {
        return this.options.maxDate instanceof DateTime
            ? date.isAfter(this.options.maxDate, 'day')
            : false;
    }
    /**
     * Handle `minDays` option
     *
     * @param date
     * @returns Boolean
     */
    lockMinDays(date, start) {
        if (this.options.minDays && start) {
            const minPrev = start
                .clone()
                .subtract(this.options.minDays - 1, 'day');
            const minNext = start
                .clone()
                .add(this.options.minDays - 1, 'day');
            return date.isBetween(minPrev, minNext);
        }
        return false;
    }
    /**
     * Handle `maxDays` option
     *
     * @param date
     * @returns Boolean
     */
    lockMaxDays(date, start) {
        if (this.options.maxDays && start) {
            const maxPrev = start
                .clone()
                .subtract(this.options.maxDays, 'day');
            const maxNext = start
                .clone()
                .add(this.options.maxDays, 'day');
            return !date.isBetween(maxPrev, maxNext);
        }
        return false;
    }
    /**
     * Handle `selectForward` option
     *
     * @param date
     * @returns Boolean
     */
    lockSelectForward(date) {
        if (this.datePicked.length === 1 && this.options.selectForward) {
            const start = this.datePicked[0].clone();
            return date.isBefore(start, 'day');
        }
        return false;
    }
    /**
     * Handle `selectBackward` option
     *
     * @param date
     * @returns Boolean
     */
    lockSelectBackward(date) {
        if (this.datePicked.length === 1 && this.options.selectBackward) {
            const start = this.datePicked[0].clone();
            return date.isAfter(start, 'day');
        }
        return false;
    }
    /**
     * Handle `filter` option
     *
     * @param date
     * @returns Boolean
     */
    testFilter(date) {
        return typeof this.options.filter === 'function'
            ? this.options.filter(date, this.datePicked)
            : false;
    }
}
class Calendar {
    constructor(picker) {
        this.picker = picker;
    }
    /**
     * Render transferred date and view
     *
     * @param date
     * @param view
     */
    render(date, view) {
        if (!date) {
            date = new DateTime();
        }
        date.setDate(1);
        date.setHours(0, 0, 0, 0);
        // find function for view
        if (typeof this[`get${view}View`] === 'function') {
            this[`get${view}View`](date);
        }
    }
    /**
     * Function for `Container` view
     *
     * @param date
     */
    getContainerView(date) {
        this.picker.ui.container.innerHTML = '';
        if (this.picker.options.header) {
            this.picker.trigger('render', { date: date.clone(), view: 'Header' });
        }
        this.picker.trigger('render', { date: date.clone(), view: 'Main' });
        if (!this.picker.options.autoApply) {
            this.picker.trigger('render', { date: date.clone(), view: 'Footer' });
        }
    }
    /**
     * Function for `Header` view
     *
     * @param date
     */
    getHeaderView(date) {
        const element = document.createElement('header');
        if (this.picker.options.header instanceof HTMLElement) {
            element.appendChild(this.picker.options.header);
        }
        if (typeof this.picker.options.header === 'string') {
            element.innerHTML = this.picker.options.header;
        }
        this.picker.ui.container.appendChild(element);
        this.picker.trigger('view', { target: element, date: date.clone(), view: 'Header' });
    }
    /**
     * Function for `Main` view
     *
     * @param date
     */
    getMainView(date) {
        const main = document.createElement('main');
        this.picker.ui.container.appendChild(main);
        const calendars = document.createElement('div');
        calendars.className = `calendars grid-${this.picker.options.grid}`;
        for (let i = 0; i < this.picker.options.calendars; i++) {
            const month = document.createElement('div');
            month.className = 'calendar';
            calendars.appendChild(month);
            const calendarHeader = this.getCalendarHeaderView(date.clone());
            month.appendChild(calendarHeader);
            this.picker.trigger('view', {
                date: date.clone(),
                view: 'CalendarHeader',
                index: i,
                target: calendarHeader,
            });
            const dayNames = this.getCalendarDayNamesView();
            month.appendChild(dayNames);
            this.picker.trigger('view', {
                date: date.clone(),
                view: 'CalendarDayNames',
                index: i,
                target: dayNames,
            });
            const daysView = this.getCalendarDaysView(date.clone());
            month.appendChild(daysView);
            this.picker.trigger('view', {
                date: date.clone(),
                view: 'CalendarDays',
                index: i,
                target: daysView,
            });
            const calendarFooter = this.getCalendarFooterView(this.picker.options.lang, date.clone());
            month.appendChild(calendarFooter);
            this.picker.trigger('view', {
                date: date.clone(),
                view: 'CalendarFooter',
                index: i,
                target: calendarFooter,
            });
            this.picker.trigger('view', {
                date: date.clone(),
                view: 'CalendarItem',
                index: i,
                target: month,
            });
            date.add(1, 'month');
        }
        main.appendChild(calendars);
        this.picker.trigger('view', { date: date.clone(), view: 'Calendars', target: calendars });
        this.picker.trigger('view', { date: date.clone(), view: 'Main', target: main });
    }
    /**
     * Function for `Footer` view
     *
     * @param date
     */
    getFooterView(date) {
        const element = document.createElement('footer');
        const buttons = document.createElement('div');
        buttons.className = 'footer-buttons';
        const cancelButton = document.createElement('button');
        cancelButton.className = 'cancel-button unit';
        cancelButton.innerHTML = this.picker.options.locale.cancel;
        buttons.appendChild(cancelButton);
        const applyButton = document.createElement('button');
        applyButton.className = 'apply-button unit';
        applyButton.innerHTML = this.picker.options.locale.apply;
        applyButton.disabled = true;
        buttons.appendChild(applyButton);
        element.appendChild(buttons);
        this.picker.ui.container.appendChild(element);
        this.picker.trigger('view', { date, target: element, view: 'Footer' });
    }
    /**
     * Function for `CalendarHeader` view
     *
     * @param date
     * @returns HTMLElement
     */
    getCalendarHeaderView(date) {
        const element = document.createElement('div');
        element.className = 'header';
        const monthName = document.createElement('div');
        monthName.className = 'month-name';
        monthName.innerHTML = `<span>${date.toLocaleString(this.picker.options.lang, { month: 'long' })}</span> ${date.format('YYYY')}`;
        element.appendChild(monthName);
        const prevMonth = document.createElement('button');
        prevMonth.className = 'previous-button unit';
        prevMonth.innerHTML = this.picker.options.locale.previousMonth;
        element.appendChild(prevMonth);
        const nextMonth = document.createElement('button');
        nextMonth.className = 'next-button unit';
        nextMonth.innerHTML = this.picker.options.locale.nextMonth;
        element.appendChild(nextMonth);
        return element;
    }
    /**
     * Function for `CalendarDayNames` view
     *
     * @param date
     * @returns HTMLElement
     */
    getCalendarDayNamesView() {
        const element = document.createElement('div');
        element.className = 'daynames-row';
        for (let w = 1; w <= 7; w++) {
            // 7 days, 4 is «Thursday» (new Date(1970, 0, 1, 12, 0, 0, 0))
            const dayIdx = 7 - 4 + this.picker.options.firstDay + w;
            const dayName = document.createElement('div');
            dayName.className = 'dayname';
            dayName.innerHTML = new Date(1970, 0, dayIdx, 12, 0, 0, 0)
                .toLocaleString(this.picker.options.lang, { weekday: 'short' });
            dayName.title = new Date(1970, 0, dayIdx, 12, 0, 0, 0)
                .toLocaleString(this.picker.options.lang, { weekday: 'long' });
            element.appendChild(dayName);
            this.picker.trigger('view', { dayIdx, view: 'CalendarDayName', target: dayName });
        }
        return element;
    }
    /**
     * Function for `CalendarDays` view
     *
     * @param date
     * @returns HTMLElement
     */
    getCalendarDaysView(date) {
        const element = document.createElement('div');
        element.className = 'days-grid';
        const offsetDays = this.calcOffsetDays(date, this.picker.options.firstDay);
        const totalDays = 32 - new Date(date.getFullYear(), date.getMonth(), 32).getDate();
        for (let idx = 0; idx < offsetDays; idx++) {
            const offsetDay = document.createElement('div');
            offsetDay.className = 'offset';
            element.appendChild(offsetDay);
        }
        for (let idx = 1; idx <= totalDays; idx++) {
            date.setDate(idx);
            const calendarDay = this.getCalendarDayView(date);
            element.appendChild(calendarDay);
            this.picker.trigger('view', { date, view: 'CalendarDay', target: calendarDay });
        }
        return element;
    }
    /**
     * Function for `CalendarDay` view
     *
     * @param date
     * @returns HTMLElement
     */
    getCalendarDayView(date) {
        const optionsDate = this.picker.options.date ? new DateTime(this.picker.options.date) : null;
        const today = new DateTime();
        const element = document.createElement('div');
        element.className = 'day unit';
        element.innerHTML = date.format('D');
        element.dataset.time = String(date.getTime());
        if (date.isSame(today, 'day')) {
            element.classList.add('today');
        }
        if ([0, 6].includes(date.getDay())) {
            element.classList.add('weekend');
        }
        if (this.picker.datePicked.length) {
            if (this.picker.datePicked[0].isSame(date, 'day')) {
                element.classList.add('selected');
            }
        }
        else if (optionsDate && date.isSame(optionsDate, 'day')) {
            element.classList.add('selected');
        }
        this.picker.trigger('view', { date, view: 'CalendarDay', target: element });
        return element;
    }
    /**
     * Function for `CalendarFooter` view
     *
     * @param lang
     * @param date
     * @returns HTMLElement
     */
    getCalendarFooterView(lang, date) {
        const element = document.createElement('div');
        element.className = 'footer';
        dummy(lang, date);
        return element;
    }
    /**
     * Count the number of days of indentation
     *
     * @param date
     * @param firstDay
     * @returns Number
     */
    calcOffsetDays(date, firstDay) {
        let total = date.getDay() - firstDay;
        if (total < 0)
            total += 7;
        return total;
    }
}
