var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { html } from 'lit';
import { eventOptions, property } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { live } from 'lit/directives/live.js';
import { convertToDate } from '../calendar/helpers.js';
import { addKeybindings, arrowDown, arrowLeft, arrowRight, arrowUp, ctrlKey, } from '../common/controllers/key-bindings.js';
import { watch } from '../common/decorators/watch.js';
import { registerComponent } from '../common/definitions/register.js';
import { EventEmitterMixin } from '../common/mixins/event-emitter.js';
import { FormValueDateTimeTransformers } from '../common/mixins/forms/form-transformers.js';
import { createFormValueState } from '../common/mixins/forms/form-value.js';
import { partMap } from '../common/part-map.js';
import { IgcMaskInputBaseComponent, } from '../mask-input/mask-input-base.js';
import IgcValidationContainerComponent from '../validation-container/validation-container.js';
import { DatePart, DateParts, DateTimeUtil, } from './date-util.js';
import { dateTimeInputValidators } from './validators.js';
class IgcDateTimeInputComponent extends EventEmitterMixin(IgcMaskInputBaseComponent) {
    static register() {
        registerComponent(IgcDateTimeInputComponent, IgcValidationContainerComponent);
    }
    get __validators() {
        return dateTimeInputValidators;
    }
    get inputFormat() {
        return this._inputFormat || this._defaultMask;
    }
    set inputFormat(val) {
        if (val) {
            this.setMask(val);
            this._inputFormat = val;
            if (this.value) {
                this.updateMask();
            }
        }
    }
    get value() {
        return this._formValue.value;
    }
    set value(value) {
        this._formValue.setValueAndFormState(value);
        this.updateMask();
    }
    set min(value) {
        this._min = convertToDate(value);
        this._validate();
    }
    get min() {
        return this._min;
    }
    set max(value) {
        this._max = convertToDate(value);
        this._validate();
    }
    get max() {
        return this._max;
    }
    setDefaultMask() {
        if (!this._inputFormat) {
            this.updateDefaultMask();
            this.setMask(this._defaultMask);
        }
        if (this.value) {
            this.updateMask();
        }
    }
    setDisplayFormat() {
        if (this.value) {
            this.updateMask();
        }
    }
    promptChange() {
        if (!this.prompt) {
            this.prompt = this.parser.prompt;
        }
        else {
            this.parser.prompt = this.prompt;
        }
    }
    get hasDateParts() {
        const parts = this._inputDateParts ||
            DateTimeUtil.parseDateTimeFormat(this.inputFormat);
        return parts.some((p) => p.type === DateParts.Date ||
            p.type === DateParts.Month ||
            p.type === DateParts.Year);
    }
    get hasTimeParts() {
        const parts = this._inputDateParts ||
            DateTimeUtil.parseDateTimeFormat(this.inputFormat);
        return parts.some((p) => p.type === DateParts.Hours ||
            p.type === DateParts.Minutes ||
            p.type === DateParts.Seconds);
    }
    get targetDatePart() {
        let result;
        if (this.focused) {
            const partType = this._inputDateParts.find((p) => p.start <= this.inputSelection.start &&
                this.inputSelection.start <= p.end &&
                p.type !== DateParts.Literal)?.type;
            if (partType) {
                result = partType;
            }
        }
        else if (this._inputDateParts.some((p) => p.type === DateParts.Date)) {
            result = DatePart.Date;
        }
        else if (this._inputDateParts.some((p) => p.type === DateParts.Hours)) {
            result = DatePart.Hours;
        }
        else {
            result = this._inputDateParts[0].type;
        }
        return result;
    }
    get datePartDeltas() {
        return Object.assign({}, this._datePartDeltas, this.spinDelta);
    }
    constructor() {
        super();
        this._formValue = createFormValueState(this, {
            initialValue: null,
            transformers: FormValueDateTimeTransformers,
        });
        this._oldValue = null;
        this._min = null;
        this._max = null;
        this._datePartDeltas = {
            date: 1,
            month: 1,
            year: 1,
            hours: 1,
            minutes: 1,
            seconds: 1,
        };
        this.spinLoop = true;
        this.locale = 'en';
        addKeybindings(this, {
            skip: () => this.readOnly,
            bindingDefaults: { triggers: ['keydownRepeat'] },
        })
            .set([ctrlKey, ';'], this.setToday)
            .set(arrowUp, this.keyboardSpin.bind(this, 'up'))
            .set(arrowDown, this.keyboardSpin.bind(this, 'down'))
            .set([ctrlKey, arrowLeft], this.navigateParts.bind(this, 0))
            .set([ctrlKey, arrowRight], this.navigateParts.bind(this, 1));
    }
    connectedCallback() {
        super.connectedCallback();
        this.updateDefaultMask();
        this.setMask(this.inputFormat);
        if (this.value) {
            this.updateMask();
        }
    }
    stepUp(datePart, delta) {
        const targetPart = datePart || this.targetDatePart;
        if (!targetPart) {
            return;
        }
        const { start, end } = this.inputSelection;
        const newValue = this.trySpinValue(targetPart, delta);
        this.value = newValue;
        this.updateComplete.then(() => this.input.setSelectionRange(start, end));
    }
    stepDown(datePart, delta) {
        const targetPart = datePart || this.targetDatePart;
        if (!targetPart) {
            return;
        }
        const { start, end } = this.inputSelection;
        const newValue = this.trySpinValue(targetPart, delta, true);
        this.value = newValue;
        this.updateComplete.then(() => this.input.setSelectionRange(start, end));
    }
    clear() {
        this.maskedValue = '';
        this.value = null;
    }
    setToday() {
        this.value = new Date();
        this.handleInput();
    }
    updateMask() {
        if (this.focused) {
            this.maskedValue = this.getMaskedValue();
        }
        else {
            if (!DateTimeUtil.isValidDate(this.value)) {
                this.maskedValue = '';
                return;
            }
            const format = this.displayFormat || this.inputFormat;
            if (this.displayFormat) {
                this.maskedValue = DateTimeUtil.formatDate(this.value, this.locale, format, true);
            }
            else if (this.inputFormat) {
                this.maskedValue = DateTimeUtil.formatDate(this.value, this.locale, format);
            }
            else {
                this.maskedValue = this.value.toLocaleString();
            }
        }
    }
    handleInput() {
        this._setTouchedState();
        this.emitEvent('igcInput', { detail: this.value?.toString() });
    }
    handleDragLeave() {
        if (!this.focused) {
            this.updateMask();
        }
    }
    handleDragEnter() {
        if (!this.focused) {
            this.maskedValue = this.getMaskedValue();
        }
    }
    async updateInput(string, range) {
        const { value, end } = this.parser.replace(this.maskedValue, string, range.start, range.end);
        this.maskedValue = value;
        this.updateValue();
        this.requestUpdate();
        if (range.start !== this.inputFormat.length) {
            this.handleInput();
        }
        await this.updateComplete;
        this.input.setSelectionRange(end, end);
    }
    trySpinValue(datePart, delta, negative = false) {
        const _delta = delta || this.datePartDeltas[datePart] || 1;
        const spinValue = negative ? -Math.abs(_delta) : Math.abs(_delta);
        return this.spinValue(datePart, spinValue);
    }
    spinValue(datePart, delta) {
        if (!(this.value && DateTimeUtil.isValidDate(this.value))) {
            return new Date();
        }
        const newDate = new Date(this.value.getTime());
        let formatPart;
        let amPmFromMask;
        switch (datePart) {
            case DatePart.Date:
                DateTimeUtil.spinDate(delta, newDate, this.spinLoop);
                break;
            case DatePart.Month:
                DateTimeUtil.spinMonth(delta, newDate, this.spinLoop);
                break;
            case DatePart.Year:
                DateTimeUtil.spinYear(delta, newDate);
                break;
            case DatePart.Hours:
                DateTimeUtil.spinHours(delta, newDate, this.spinLoop);
                break;
            case DatePart.Minutes:
                DateTimeUtil.spinMinutes(delta, newDate, this.spinLoop);
                break;
            case DatePart.Seconds:
                DateTimeUtil.spinSeconds(delta, newDate, this.spinLoop);
                break;
            case DatePart.AmPm:
                formatPart = this._inputDateParts.find((dp) => dp.type === DateParts.AmPm);
                if (formatPart !== undefined) {
                    amPmFromMask = this.maskedValue.substring(formatPart.start, formatPart.end);
                    return DateTimeUtil.spinAmPm(newDate, this.value, amPmFromMask);
                }
                break;
        }
        return newDate;
    }
    async onWheel(event) {
        if (!this.focused || this.readOnly) {
            return;
        }
        event.preventDefault();
        event.stopPropagation();
        const { start, end } = this.inputSelection;
        event.deltaY > 0 ? this.stepDown() : this.stepUp();
        this.handleInput();
        await this.updateComplete;
        this.setSelectionRange(start, end);
    }
    updateDefaultMask() {
        this._defaultMask = DateTimeUtil.getDefaultMask(this.locale);
    }
    setMask(string) {
        const oldFormat = this._inputDateParts?.map((p) => p.format).join('');
        this._inputDateParts = DateTimeUtil.parseDateTimeFormat(string);
        const value = this._inputDateParts.map((p) => p.format).join('');
        this._defaultMask = value;
        const newMask = (value || DateTimeUtil.DEFAULT_INPUT_FORMAT).replace(new RegExp(/(?=[^t])[\w]/, 'g'), '0');
        this._mask = newMask.includes('tt')
            ? newMask.replace(/tt/g, 'LL')
            : newMask;
        this.parser.mask = this._mask;
        this.parser.prompt = this.prompt;
        if (!this.placeholder || oldFormat === this.placeholder) {
            this.placeholder = value;
        }
    }
    parseDate(val) {
        return val
            ? DateTimeUtil.parseValueFromMask(val, this._inputDateParts, this.prompt)
            : null;
    }
    getMaskedValue() {
        let mask = this.emptyMask;
        if (DateTimeUtil.isValidDate(this.value)) {
            for (const part of this._inputDateParts) {
                if (part.type === DateParts.Literal) {
                    continue;
                }
                const targetValue = DateTimeUtil.getPartValue(part, part.format.length, this.value);
                mask = this.parser.replace(mask, targetValue, part.start, part.end).value;
            }
            return mask;
        }
        return this.maskedValue === '' ? mask : this.maskedValue;
    }
    isComplete() {
        return !this.maskedValue.includes(this.prompt);
    }
    updateValue() {
        if (this.isComplete()) {
            const parsedDate = this.parseDate(this.maskedValue);
            this.value = DateTimeUtil.isValidDate(parsedDate) ? parsedDate : null;
        }
        else {
            this.value = null;
        }
    }
    _updateSetRangeTextValue() {
        this.updateValue();
    }
    getNewPosition(value, direction = 0) {
        const cursorPos = this.selection.start;
        if (!direction) {
            const part = this._inputDateParts.findLast((part) => part.type === DateParts.Literal && part.end < cursorPos);
            return part?.end ?? 0;
        }
        const part = this._inputDateParts.find((part) => part.type === DateParts.Literal && part.start > cursorPos);
        return part?.start ?? value.length;
    }
    async handleFocus() {
        this.focused = true;
        if (this.readOnly) {
            return;
        }
        this._oldValue = this.value;
        const areFormatsDifferent = this.displayFormat !== this.inputFormat;
        if (!this.value) {
            this.maskedValue = this.emptyMask;
            await this.updateComplete;
            this.select();
        }
        else if (areFormatsDifferent) {
            this.updateMask();
        }
    }
    handleBlur() {
        const isEmptyMask = this.maskedValue === this.emptyMask;
        this.focused = false;
        if (!(this.isComplete() || isEmptyMask)) {
            const parse = this.parseDate(this.maskedValue);
            if (parse) {
                this.value = parse;
            }
            else {
                this.value = null;
                this.maskedValue = '';
            }
        }
        else {
            this.updateMask();
        }
        const isSameValue = this._oldValue === this.value;
        if (!(this.readOnly || isSameValue)) {
            this.emitEvent('igcChange', { detail: this.value });
        }
        super._handleBlur();
    }
    navigateParts(delta) {
        const position = this.getNewPosition(this.input.value, delta);
        this.setSelectionRange(position, position);
    }
    async keyboardSpin(direction) {
        direction === 'up' ? this.stepUp() : this.stepDown();
        this.handleInput();
        await this.updateComplete;
        this.setSelectionRange(this.selection.start, this.selection.end);
    }
    renderInput() {
        return html `
      <input
        type="text"
        part=${partMap(this.resolvePartNames('input'))}
        name=${ifDefined(this.name)}
        .value=${live(this.maskedValue)}
        .placeholder=${live(this.placeholder || this.emptyMask)}
        ?readonly=${this.readOnly}
        ?disabled=${this.disabled}
        @blur=${this.handleBlur}
        @focus=${this.handleFocus}
        @input=${super.handleInput}
        @wheel=${this.onWheel}
        @keydown=${super.handleKeydown}
        @click=${this.handleClick}
        @cut=${this.handleCut}
        @compositionstart=${this.handleCompositionStart}
        @compositionend=${this.handleCompositionEnd}
        @dragenter=${this.handleDragEnter}
        @dragleave=${this.handleDragLeave}
        @dragstart=${this.handleDragStart}
      />
    `;
    }
}
IgcDateTimeInputComponent.tagName = 'igc-date-time-input';
export default IgcDateTimeInputComponent;
__decorate([
    property({ attribute: 'input-format' })
], IgcDateTimeInputComponent.prototype, "inputFormat", null);
__decorate([
    property({ converter: convertToDate })
], IgcDateTimeInputComponent.prototype, "value", null);
__decorate([
    property({ converter: convertToDate })
], IgcDateTimeInputComponent.prototype, "min", null);
__decorate([
    property({ converter: convertToDate })
], IgcDateTimeInputComponent.prototype, "max", null);
__decorate([
    property({ attribute: 'display-format' })
], IgcDateTimeInputComponent.prototype, "displayFormat", void 0);
__decorate([
    property({ attribute: false })
], IgcDateTimeInputComponent.prototype, "spinDelta", void 0);
__decorate([
    property({ type: Boolean, attribute: 'spin-loop' })
], IgcDateTimeInputComponent.prototype, "spinLoop", void 0);
__decorate([
    property()
], IgcDateTimeInputComponent.prototype, "locale", void 0);
__decorate([
    watch('locale', { waitUntilFirstUpdate: true })
], IgcDateTimeInputComponent.prototype, "setDefaultMask", null);
__decorate([
    watch('displayFormat', { waitUntilFirstUpdate: true })
], IgcDateTimeInputComponent.prototype, "setDisplayFormat", null);
__decorate([
    watch('prompt', { waitUntilFirstUpdate: true })
], IgcDateTimeInputComponent.prototype, "promptChange", null);
__decorate([
    eventOptions({ passive: false })
], IgcDateTimeInputComponent.prototype, "onWheel", null);
//# sourceMappingURL=date-time-input.js.map