import { __decorate, __metadata } from "tslib";
import { h, Host, } from "@stencil/core";
import isEmpty from "lodash.isempty";
import isNaN from "lodash.isnan";
import isNil from "lodash.isnil";
import { ariaBooleanToString } from "../../utils/aria";
import { inheritAttributes } from "../../utils/attributes";
import { BEM } from "../../utils/bem";
import { defaultConfig, ListenToConfig, } from "../../utils/config";
import { defaultBalAriaForm } from "../../utils/form";
import { getNativeInputValue, getUpcomingValue, inputHandleBlur, inputHandleChange, inputHandleClick, inputHandleFocus, inputHandleHostClick, inputHandleReset, inputListenOnClick, inputSetBlur, inputSetFocus, stopEventBubbling, } from "../../utils/form-input";
import { debounceEvent } from "../../utils/helpers";
import { Logger } from "../../utils/log";
import { getDecimalSeparator, getThousandSeparator } from "../../utils/number";
import { isNotNumber, mapDecimalSeparator, toFixedNumber, toNumber, toUserFormattedNumber, validateKeyDown, } from "./bal-number-input.utils";
export class NumberInput {
    constructor() {
        this.inputId = `bal-number-input-${numberInputIds++}`;
        this.inheritedAttributes = {};
        this.lastValue = '';
        this.inputValue = this.value;
        this.initialValue = undefined;
        this.focused = false;
        this.language = defaultConfig.language;
        this.region = defaultConfig.region;
        this.ariaForm = defaultBalAriaForm;
        this.nativeInputValue = '';
        this.inputPattern = this.createPattern();
        /**
         * PUBLIC PROPERTY API
         * ------------------------------------------------------
         */
        /**
         * The name of the control, which is submitted with the form data.
         */
        this.name = this.inputId;
        /**
         * If `true` the component gets a invalid style.
         */
        this.invalid = false;
        /**
         * Defines the allowed decimal points for the `number-input`.
         */
        this.decimal = 0;
        /**
         * Allows only positive number values.
         */
        this.onlyPositive = false;
        /**
         * If `true`, the user must fill in a value before submitting a form.
         */
        this.required = false;
        /**
         * If `true`, the element is not mutable, focusable, or even submitted with the form. The user can neither edit nor focus on the control, nor its form control descendants.
         */
        this.disabled = false;
        /**
         * If `true` the element can not mutated, meaning the user can not edit the control.
         */
        this.readonly = false;
        /**
         * If `true` the input value has 0 as default value
         */
        this.exactNumber = false;
        /**
         * Set the amount of time, in milliseconds, to wait to trigger the `balChange` event after each keystroke. This also impacts form bindings such as `ngModel` or `v-model`.
         */
        this.debounce = 0;
        /**
         * The value of the input.
         */
        this.value = undefined;
        /**
         * If `true`, in Angular reactive forms the control will not be set invalid
         */
        this.autoInvalidOff = false;
        /**
         * EVENT BINDING
         * ------------------------------------------------------
         */
        this.onClick = (ev) => inputHandleClick(this, ev);
        this.handleClick = (ev) => inputHandleHostClick(this, ev);
        this.onInput = (_ev) => {
            var _a;
            //
            // if the new value is not a number, the last value will be restored
            if (this.nativeInput) {
                if (isNotNumber(this.nativeInput.value)) {
                    this.nativeInputValue = this.lastValue || '';
                    return;
                }
            }
            //
            // if new value is accepted the last value gets updated and input event will be fired
            this.lastValue = ((_a = this.nativeInput) === null || _a === void 0 ? void 0 : _a.value) || '';
            this.balInput.emit(toNumber(this.lastValue, this.decimal));
        };
        this.onFocus = (ev) => {
            inputHandleFocus(this, ev);
            //
            // restore the input with the last user value without the formatting
            if (this.nativeInput) {
                this.nativeInputValue = mapDecimalSeparator(this.lastValue || '');
                clearTimeout(this.selectTimeout);
                this.selectTimeout = setTimeout(() => this.nativeInput.select());
            }
        };
        this.onBlur = (ev) => {
            inputHandleBlur(this, ev);
            //
            // on focus out the input value gets a pretty format
            if (this.nativeInput) {
                this.lastValue = toFixedNumber(this.lastValueGetter, this.decimal);
                this.nativeInputValue = toUserFormattedNumber(this.lastValueGetter, this.decimal, this.suffix);
                this.nativeInput.value = this.nativeInputValue;
            }
            this.inputValue = toNumber(this.lastValueGetter, this.decimal);
            inputHandleChange(this);
        };
        this.onKeydown = (ev) => {
            const oldValue = getNativeInputValue(this);
            const newValue = getUpcomingValue(this, ev);
            const input = ev === null || ev === void 0 ? void 0 : ev.target;
            if (input &&
                !validateKeyDown({
                    key: ev.key,
                    ctrlKey: ev.ctrlKey,
                    metaKey: ev.metaKey,
                    decimal: this.decimal,
                    newValue,
                    oldValue,
                    selectionStart: input.selectionStart,
                    selectionEnd: input.selectionEnd,
                    onlyPositive: this.onlyPositive,
                })) {
                return stopEventBubbling(ev);
            }
        };
    }
    createLogger(log) {
        this.log = log;
    }
    debounceChanged() {
        this.balChange = debounceEvent(this.balChange, this.debounce);
    }
    valueChanged(newValue, oldValue) {
        const newValueAsNumber = toNumber(newValue, this.decimal);
        if (newValueAsNumber !== oldValue) {
            const isValueNotDefined = newValueAsNumber === '' || isNil(newValueAsNumber) || isNaN(newValueAsNumber);
            const emptyValue = this.exactNumber ? '0' : '';
            const value = isValueNotDefined ? emptyValue : newValueAsNumber.toString();
            this.inputValue = toNumber(toFixedNumber(value, this.decimal), this.decimal);
            this.lastValue = toFixedNumber(value, this.decimal);
            if (this.focused) {
                this.nativeInputValue = mapDecimalSeparator(this.lastValue);
            }
            else {
                this.nativeInputValue = toUserFormattedNumber(this.lastValue, this.decimal, this.suffix);
            }
        }
    }
    /**
     * LIFECYCLE
     * ------------------------------------------------------
     */
    connectedCallback() {
        this.debounceChanged();
        this.initialValue = this.value || 0;
        if (this.value !== undefined) {
            this.valueChanged(this.value, undefined);
        }
    }
    componentDidLoad() {
        this.inputValue = this.value;
    }
    componentWillLoad() {
        this.inheritedAttributes = inheritAttributes(this.el, ['aria-label', 'tabindex', 'title']);
    }
    /**
     * LISTENERS
     * ------------------------------------------------------
     */
    listenOnClick(ev) {
        inputListenOnClick(this, ev);
    }
    resetHandler(ev) {
        const formElement = ev.target;
        if (formElement === null || formElement === void 0 ? void 0 : formElement.contains(this.el)) {
            inputHandleReset(this, this.initialValue, this.resetHandlerTimer);
        }
    }
    /**
     * PUBLIC METHODS
     * ------------------------------------------------------
     */
    /**
     * @internal define config for the component
     */
    async configChanged(state) {
        this.language = state.language;
        this.region = state.region;
        this.inputPattern = this.createPattern();
        if (this.nativeInput) {
            if (this.focused) {
                this.nativeInputValue = mapDecimalSeparator(this.lastValue);
            }
            else {
                this.nativeInputValue = toUserFormattedNumber(this.lastValue, this.decimal, this.suffix);
            }
        }
    }
    /**
     * Sets focus on the native `input`. Use this method instead of the global
     * `input.focus()`.
     */
    async setFocus() {
        inputSetFocus(this);
    }
    /**
     * Sets blur on the native `input`. Use this method instead of the global
     * `input.blur()`.
     * @internal
     */
    async setBlur() {
        inputSetBlur(this);
    }
    /**
     * Returns the native `<input>` element used under the hood.
     */
    getInputElement() {
        return Promise.resolve(this.nativeInput);
    }
    /**
     * @internal
     */
    async setAriaForm(ariaForm) {
        this.ariaForm = Object.assign({}, ariaForm);
    }
    /**
     * GETTERS
     * ------------------------------------------------------
     */
    createPattern() {
        if (this.pattern) {
            return this.pattern;
        }
        let suffix = this.suffix || '';
        if (suffix !== '') {
            suffix = ` ${suffix}`;
        }
        const thousandSeparator = getThousandSeparator();
        let decimalSeparator = getDecimalSeparator();
        if (decimalSeparator === ',') {
            decimalSeparator = '\\,';
        }
        let negativeSymbol = '-';
        if (this.onlyPositive) {
            negativeSymbol = '';
        }
        return `${negativeSymbol}?\\d{1,3}(?:${thousandSeparator}\\d{3})*(?:\\${decimalSeparator}\\d{1,2})?(?:${suffix})?`;
    }
    get lastValueGetter() {
        if (this.exactNumber && (isNil(this.lastValue) || isEmpty(this.lastValue))) {
            return '0';
        }
        return this.lastValue;
    }
    /**
     * RENDER
     * ------------------------------------------------------
     */
    render() {
        const block = BEM.block('number-input');
        return (h(Host, { key: 'd91080decce4a1568a02ca2dd035866aae49cf76', onClick: this.handleClick, "aria-disabled": ariaBooleanToString(this.disabled), class: Object.assign({}, block.class()) }, h("input", Object.assign({ key: '88efec9e37fd84e1aba4672521a93f5453105bcd', class: {
                'input': true,
                'is-disabled': this.disabled || this.readonly,
                'is-danger': this.invalid,
            }, "data-testid": "bal-number-input", ref: input => (this.nativeInput = input), id: this.ariaForm.controlId || this.inputId, "aria-labelledby": this.ariaForm.labelId, "aria-describedby": this.ariaForm.messageId, "aria-invalid": this.invalid === true ? 'true' : 'false', "aria-disabled": ariaBooleanToString(this.disabled), name: this.name, disabled: this.disabled, placeholder: this.placeholder || '', readonly: this.readonly, required: this.required, pattern: this.inputPattern, min: this.min, max: this.max, value: this.nativeInputValue, onInput: e => this.onInput(e), onFocus: e => this.onFocus(e), onBlur: e => this.onBlur(e), onClick: this.onClick, onKeyDown: e => this.onKeydown(e), onKeyPress: e => this.balKeyPress.emit(e) }, this.inheritedAttributes))));
    }
    static get is() { return "bal-number-input"; }
    static get originalStyleUrls() {
        return {
            "$": ["bal-number-input.sass"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["bal-number-input.css"]
        };
    }
    static get properties() {
        return {
            "name": {
                "type": "string",
                "attribute": "name",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The name of the control, which is submitted with the form data."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "this.inputId"
            },
            "invalid": {
                "type": "boolean",
                "attribute": "invalid",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true` the component gets a invalid style."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "false"
            },
            "decimal": {
                "type": "number",
                "attribute": "decimal",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Defines the allowed decimal points for the `number-input`."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "0"
            },
            "onlyPositive": {
                "type": "boolean",
                "attribute": "only-positive",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Allows only positive number values."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "false"
            },
            "suffix": {
                "type": "string",
                "attribute": "suffix",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Adds a suffix the the input-value after blur."
                },
                "getter": false,
                "setter": false,
                "reflect": false
            },
            "pattern": {
                "type": "string",
                "attribute": "pattern",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "A regular expression that the value is checked against. The pattern must match the entire value, not just some subset. Use the title attribute to describe the pattern to help the user. This attribute applies when the value of the type attribute is `\"text\"`, `\"search\"`, `\"tel\"`, `\"url\"`, `\"email\"`, `\"date\"`, or `\"password\"`, otherwise it is ignored. When the type attribute is `\"date\"`, `pattern` will only be used in browsers that do not support the `\"date\"` input type natively. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date for more information."
                },
                "getter": false,
                "setter": false,
                "reflect": false
            },
            "placeholder": {
                "type": "string",
                "attribute": "placeholder",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Instructional text that shows before the input has a value."
                },
                "getter": false,
                "setter": false,
                "reflect": false
            },
            "required": {
                "type": "boolean",
                "attribute": "required",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, the user must fill in a value before submitting a form."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "false"
            },
            "disabled": {
                "type": "boolean",
                "attribute": "disabled",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, the element is not mutable, focusable, or even submitted with the form. The user can neither edit nor focus on the control, nor its form control descendants."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "false"
            },
            "readonly": {
                "type": "boolean",
                "attribute": "readonly",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true` the element can not mutated, meaning the user can not edit the control."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "false"
            },
            "exactNumber": {
                "type": "boolean",
                "attribute": "exact-number",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true` the input value has 0 as default value"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "false"
            },
            "max": {
                "type": "string",
                "attribute": "max",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "The maximum value, which must not be less than its minimum (min attribute) value."
                },
                "getter": false,
                "setter": false,
                "reflect": false
            },
            "min": {
                "type": "string",
                "attribute": "min",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "The minimum value, which must not be greater than its maximum (max attribute) value."
                },
                "getter": false,
                "setter": false,
                "reflect": false
            },
            "debounce": {
                "type": "number",
                "attribute": "debounce",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Set the amount of time, in milliseconds, to wait to trigger the `balChange` event after each keystroke. This also impacts form bindings such as `ngModel` or `v-model`."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "0"
            },
            "value": {
                "type": "any",
                "attribute": "value",
                "mutable": true,
                "complexType": {
                    "original": "number | string",
                    "resolved": "number | string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "The value of the input."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "undefined"
            },
            "autoInvalidOff": {
                "type": "boolean",
                "attribute": "auto-invalid-off",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, in Angular reactive forms the control will not be set invalid"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "defaultValue": "false"
            }
        };
    }
    static get states() {
        return {
            "focused": {},
            "language": {},
            "region": {},
            "ariaForm": {},
            "nativeInputValue": {},
            "inputPattern": {}
        };
    }
    static get events() {
        return [{
                "method": "balInput",
                "name": "balInput",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when a keyboard input occurred."
                },
                "complexType": {
                    "original": "BalEvents.BalNumberInputInputDetail",
                    "resolved": "number",
                    "references": {
                        "BalEvents": {
                            "location": "global",
                            "id": "global::BalEvents"
                        }
                    }
                }
            }, {
                "method": "balChange",
                "name": "balChange",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the value has changed."
                },
                "complexType": {
                    "original": "BalEvents.BalNumberInputChangeDetail",
                    "resolved": "number",
                    "references": {
                        "BalEvents": {
                            "location": "global",
                            "id": "global::BalEvents"
                        }
                    }
                }
            }, {
                "method": "balBlur",
                "name": "balBlur",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the input loses focus."
                },
                "complexType": {
                    "original": "BalEvents.BalNumberInputBlurDetail",
                    "resolved": "FocusEvent",
                    "references": {
                        "BalEvents": {
                            "location": "global",
                            "id": "global::BalEvents"
                        }
                    }
                }
            }, {
                "method": "balFocus",
                "name": "balFocus",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the input has focus."
                },
                "complexType": {
                    "original": "BalEvents.BalNumberInputFocusDetail",
                    "resolved": "FocusEvent",
                    "references": {
                        "BalEvents": {
                            "location": "global",
                            "id": "global::BalEvents"
                        }
                    }
                }
            }, {
                "method": "balKeyPress",
                "name": "balKeyPress",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when a keyboard key has pressed."
                },
                "complexType": {
                    "original": "BalEvents.BalNumberInputKeyPressDetail",
                    "resolved": "KeyboardEvent",
                    "references": {
                        "BalEvents": {
                            "location": "global",
                            "id": "global::BalEvents"
                        }
                    }
                }
            }];
    }
    static get methods() {
        return {
            "configChanged": {
                "complexType": {
                    "signature": "(state: BalConfigState) => Promise<void>",
                    "parameters": [{
                            "name": "state",
                            "type": "BalConfigState",
                            "docs": ""
                        }],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        },
                        "BalConfigState": {
                            "location": "import",
                            "path": "../../utils/config",
                            "id": "src/utils/config/index.ts::BalConfigState"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "",
                    "tags": [{
                            "name": "internal",
                            "text": "define config for the component"
                        }]
                }
            },
            "setFocus": {
                "complexType": {
                    "signature": "() => Promise<void>",
                    "parameters": [],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "Sets focus on the native `input`. Use this method instead of the global\n`input.focus()`.",
                    "tags": []
                }
            },
            "setBlur": {
                "complexType": {
                    "signature": "() => Promise<void>",
                    "parameters": [],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "Sets blur on the native `input`. Use this method instead of the global\n`input.blur()`.",
                    "tags": [{
                            "name": "internal",
                            "text": undefined
                        }]
                }
            },
            "getInputElement": {
                "complexType": {
                    "signature": "() => Promise<HTMLInputElement>",
                    "parameters": [],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        },
                        "HTMLInputElement": {
                            "location": "global",
                            "id": "global::HTMLInputElement"
                        }
                    },
                    "return": "Promise<HTMLInputElement>"
                },
                "docs": {
                    "text": "Returns the native `<input>` element used under the hood.",
                    "tags": []
                }
            },
            "setAriaForm": {
                "complexType": {
                    "signature": "(ariaForm: BalAriaForm) => Promise<void>",
                    "parameters": [{
                            "name": "ariaForm",
                            "type": "BalAriaForm",
                            "docs": ""
                        }],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        },
                        "BalAriaForm": {
                            "location": "import",
                            "path": "../../utils/form",
                            "id": "src/utils/form.ts::BalAriaForm"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "",
                    "tags": [{
                            "name": "internal",
                            "text": undefined
                        }]
                }
            }
        };
    }
    static get elementRef() { return "el"; }
    static get watchers() {
        return [{
                "propName": "debounce",
                "methodName": "debounceChanged"
            }, {
                "propName": "value",
                "methodName": "valueChanged"
            }];
    }
    static get listeners() {
        return [{
                "name": "click",
                "method": "listenOnClick",
                "target": "document",
                "capture": true,
                "passive": false
            }, {
                "name": "reset",
                "method": "resetHandler",
                "target": "document",
                "capture": true,
                "passive": false
            }];
    }
}
__decorate([
    Logger('bal-number-input'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Function]),
    __metadata("design:returntype", void 0)
], NumberInput.prototype, "createLogger", null);
__decorate([
    ListenToConfig(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], NumberInput.prototype, "configChanged", null);
let numberInputIds = 0;
