import * as i0 from '@angular/core';
import { input, numberAttribute, booleanAttribute, inject, ElementRef, Renderer2, signal, LOCALE_ID, PLATFORM_ID, effect, computed, HostListener, HostBinding, Component, forwardRef, Directive } from '@angular/core';
import { injectOptional, LocaleService } from '@eui/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import Cleave from 'cleave.js';
import { InputDirective } from '@eui/components/shared';
import { DOCUMENT, isPlatformBrowser, isPlatformServer } from '@angular/common';
import * as i1 from '@eui/components/directives';
import { EuiClearableDirective, EuiLoadingDirective } from '@eui/components/directives';
import { NgControl, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';

/**
 * Get the decimal separator based on the locale
 *
 * @param locale value of the locale based on ISO 639-1
 */
function getDecimal(locale) {
    return getLocaleConfig(locale).decimal;
}
/**
 * Get the group separator based on the locale
 *
 * @param locale value of the locale based on ISO 639-1
 */
function getGroup(locale) {
    return getLocaleConfig(locale).group;
}
/**
 * Returns the decimal and group separators for a given locale
 *
 * @param locale value of the locale based on ISO 639-1
 */
function getLocaleConfig(locale) {
    const parts = new Intl.NumberFormat(locale, { useGrouping: true }).formatToParts(10000.1);
    return {
        group: parts.find(p => p.type === 'group')?.value || '',
        decimal: parts.find(p => p.type === 'decimal')?.value || '.',
    };
}
/**
 * @description
 * Input number component that allows the user to enter a number. It supports number
 * formatting and validation. It uses Cleave.js to format the number. It depends on
 * the {@link LocaleService} to get the current locale and format the number accordingly.
 *
 * It supports the following attributes:
 * - {@link min}: The minimum number can be entered. Blocks user's input if not in range.
 * - {@link max}: The maximum number can be entered. Blocks user's input if not in range.
 * - {@link leadingZero}: Adds leading zero to a number. Formatting will not work.
 * - {@link isInvalid}: Sets the invalid state of the input element.
 * - {@link fractionDigits}: Determines how many digits to show after the decimal point.
 * - {@link digits}: Determines how many digits to show before the decimal point.
 * - {@link fillFraction}: Fills the decimal part with zeros in case it's less than the fractionDigits.
 * - {@link roundUp}: Rounds a number with more than two decimals UP.
 * - {@link noFormat}: Disables the number formatting. It will be treated as a plain number.
 * - {@link value}: The value of the input element.
 * - {@link placeholder}: The placeholder value of the input element.
 * - {@link euiClearable}: Adds a clear button to the input element.
 * - {@link euiLoading}: Adds a loading spinner to the input element.
 * - {@link readonly}: Disables the input element.
 * - {@link disabled}: Disables the input element.
 * - {@link euiDanger}: Sets the invalid state of the input element.
 * - {@link euiSuccess}: Sets the success state of the input element.
 * - {@link euiWarning}: Sets the warning state of the input element.
 * - {@link euiInfo}: Sets the info state of the input element.
 * - {@link euiPrimary}: Sets the primary state of the input element.
 * - {@link euiSecondary}: Sets the secondary state of the input element.
 *
 * @usageNotes
 * ### Basic Usage
 * ```html
 * <input euiInputNumber [(ngModel)]="amount" [fractionDigits]="2" />
 * ```
 *
 * ### With Min/Max Range
 * ```html
 * <input euiInputNumber [min]="0" [max]="100" [(ngModel)]="percentage" />
 * ```
 *
 * ### Currency Format
 * ```html
 * <input euiInputNumber [fractionDigits]="2" [fillFraction]="true" placeholder="0.00" />
 * ```
 *
 * ### Accessibility
 * - Use associated `<label>` with `for` attribute
 * - Invalid state is communicated via `aria-invalid`
 * - Min/max constraints are enforced during input
 *
 * ### Notes
 * - Automatically formats numbers based on current locale
 * - Supports large numbers as strings to avoid precision loss
 * - Use `fillFraction` for consistent decimal display (e.g., currency)
 * - `roundUp` parameter controls decimal rounding behavior
 */
class EuiInputNumberComponent extends InputDirective {
    /**
     * CSS classes to be added to the host element.
     */
    get cssClasses() {
        return [super.getCssClasses('eui-input-number'), this._isInvalid() ? 'eui-input-number--invalid' : ''].join(' ').trim();
    }
    constructor() {
        super();
        /**
         * The minimum number can be entered. Blocks user's input if not in range.
         */
        this.min = input(undefined, { ...(ngDevMode ? { debugName: "min" } : {}), transform: numberAttribute });
        /**
         * The maximum number can be entered. Blocks user's input if not in range.
         */
        this.max = input(undefined, { ...(ngDevMode ? { debugName: "max" } : {}), transform: numberAttribute });
        /**
         * Adds leading zero to a number. Formatting will not work.
         *      e.g. with leadingZero=3 input number 5 => 005
         * @default 0
         */
        this.leadingZero = input(0, { ...(ngDevMode ? { debugName: "leadingZero" } : {}), transform: (v) => numberAttribute(v, 0) });
        this.isInvalid = input(undefined, { ...(ngDevMode ? { debugName: "isInvalid" } : {}), transform: this.invalidTransform });
        /**
         *  determines how many digits to show after the decimal point
         *  @default 0
         */
        this.fractionDigits = input(0, { ...(ngDevMode ? { debugName: "fractionDigits" } : {}), transform: (v) => numberAttribute(v, 0) });
        /**
         * determines how many digits to show before the decimal point
         */
        this.digits = input(undefined, { ...(ngDevMode ? { debugName: "digits" } : {}), transform: numberAttribute });
        /**
         * fills the decimal part with zeros in case it's less than the fractionDigits
         */
        this.fillFraction = input(undefined, { ...(ngDevMode ? { debugName: "fillFraction" } : {}), transform: booleanAttribute });
        /**
         * rounds a number with more than two decimals UP
         */
        this.roundUp = input(undefined, { ...(ngDevMode ? { debugName: "roundUp" } : {}), transform: numberAttribute });
        /**
         * Disables the number formatting. It will be treated as a plain number.
         * Will do a thousand grouping of number.
         */
        this.noFormat = input(undefined, { ...(ngDevMode ? { debugName: "noFormat" } : {}), transform: booleanAttribute });
        /**
         * The type of the input element.
         * @default 'eui-number'
         */
        this.type = 'eui-number';
        /**
         * The value of the input element.
         */
        this.value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
        this._elementRef = inject(ElementRef);
        this._renderer = inject(Renderer2);
        /**
         * Signal that tracks the invalid state of the associated form control.
         * Used to reactively update the component's visual state when the form control's validity changes.
         * @internal
         */
        this.controlInvalid = signal(false, ...(ngDevMode ? [{ debugName: "controlInvalid" }] : []));
        /**
         * Signal that tracks whether the associated form control has been touched.
         * Used to determine when to show validation messages.
         * Initialized with the control's touched state if available.
         * @internal
         */
        this.controlTouched = signal(this.control?.touched, ...(ngDevMode ? [{ debugName: "controlTouched" }] : []));
        /** @internal */
        this._isInvalid = signal(false, ...(ngDevMode ? [{ debugName: "_isInvalid" }] : []));
        this.localeService = injectOptional(LocaleService);
        this.locale_id = inject(LOCALE_ID);
        this.document = inject(DOCUMENT);
        this.platformId = inject(PLATFORM_ID);
        this.onChange = (_) => {
            if (this.control && (_ !== null || _ !== undefined)) {
                // parse number
                // const value = Number(_);
                // run on next MicroTask to avoid NG0100 error TODO: deeper investigation
                // Promise.resolve().then(() => this.control.viewToModelUpdate(value));
                // in case control is FormControl
                // this.control?.control.setValue(isNaN(value) ? null : value, { emitEvent: false, emitModelToViewChange: false });
            }
        };
        this.onTouched = () => {
            this.control?.control.markAsTouched();
        };
        const locale_id = this.locale_id;
        // set default options
        this.options = {
            numeral: true,
            delimiter: getGroup(locale_id || 'en'),
            numeralDecimalMark: getDecimal(locale_id || 'en'),
            numeralDecimalScale: this.fractionDigits(),
            numeralIntegerScale: this.digits(),
        };
        effect(() => {
            this.euiDanger = this._isInvalid();
        });
    }
    ngOnInit() {
        super.ngOnInit();
        this.control = this.injector.get(NgControl, undefined, { optional: true });
        // instantiate cleave
        if (!this.control) {
            this.initCleave();
        }
        // subscribe to localization service and listen for locale change. De-construction of locale should happen here!
        this.localeService?.getState().subscribe((state) => {
            this.options = this.getCleaveOptsBasedOnLocale(state?.id);
            // update the options on the cleave instance
            this.updateOptions(true);
        });
        this._renderer.setProperty(this._elementRef.nativeElement, 'rootClassName', 'eui-input-number');
        this._isInvalid = computed(() => {
            const controlTouched = this.controlTouched();
            const controlInvalid = this.controlInvalid();
            const isInvalid = this.isInvalid();
            // Check if the control is invalid and touched
            return this.control ? controlInvalid && controlTouched : isInvalid;
        }, ...(ngDevMode ? [{ debugName: "_isInvalid" }] : []));
    }
    ngDoCheck() {
        if (!this.control) {
            this.control = this.injector.get(NgControl, undefined, { optional: true });
        }
        if (this.control) {
            this.controlInvalid.set(this.control.invalid);
            this.controlTouched.set(this.control.touched);
        }
    }
    ngOnChanges(changes) {
        if (Object.hasOwn(changes, 'value')) {
            this._elementRef.nativeElement.value = this.value();
            if (this.cleaveInstance) {
                this.cleaveInstance.setRawValue(this.value());
            }
        }
        if (Object.hasOwn(changes, 'fractionDigits')) {
            this.options = { ...this.options, numeralDecimalScale: this.fractionDigits() };
            this.updateOptions();
        }
        if (Object.hasOwn(changes, 'noFormat')) {
            this.options = { ...this.options, numeralThousandsGroupStyle: this.noFormat() ? 'none' : 'thousand' };
            this.updateOptions();
        }
        if (Object.hasOwn(changes, 'fillFraction') && this.cleaveInstance) {
            this.applyFractionFill(this.cleaveInstance.getRawValue());
        }
        if (Object.hasOwn(changes, 'digits')) {
            this.options = { ...this.options, numeralIntegerScale: this.digits() };
            this.updateOptions();
        }
        if (Object.hasOwn(changes, 'min')) {
            if (changes['min'].currentValue >= 0) {
                if (this.cleaveInstance) {
                    // take care of the sign symbol
                    this.cleaveInstance.setRawValue(Math.abs(Number(this.cleaveInstance.getRawValue())).toString());
                }
            }
        }
        if (Object.hasOwn(changes, 'max')) {
            // check if current value is in range
            if (this.cleaveInstance) {
                const number = Number(this.cleaveInstance.getRawValue());
                if (number > this.max()) {
                    this.cleaveInstance.setRawValue(this.max().toString());
                }
            }
        }
        if (Object.hasOwn(changes, 'roundUp')) {
            if (changes['roundUp'].currentValue > 0) {
                if (this.cleaveInstance) {
                    let number = this.parseNumber(this.cleaveInstance.getRawValue(), false, false);
                    number = this.decimalAdjust(number, this.roundUp());
                    this.cleaveInstance.setRawValue(number.toString());
                }
            }
        }
        if (Object.hasOwn(changes, 'placeholder')) {
            // parse number
            const number = this.parseNumber(changes['placeholder'].currentValue?.toString() || undefined);
            // set placeholder in case none is provided
            this.setPlaceholderAttribute(!this.isItaNumber(number.toString()) ? changes['placeholder'].currentValue : number);
        }
    }
    ngOnDestroy() {
        super.ngOnDestroy();
        // destroy cleave instance
        if (this.cleaveInstance) {
            this.cleaveInstance.destroy();
        }
    }
    onPaste(event) {
        // get the value from clipboard
        const value = (event.clipboardData || window['clipboardData']).getData('text');
        // in case round UP is enabled
        if (this.roundUp() > 0) {
            const div = this.document.createElement('input');
            // create a clone of cleaveInstance
            const cloneInstance = new Cleave(div, { ...this.options, numeralDecimalScale: 8 });
            cloneInstance.setRawValue(value.toString());
            let number = this.parseNumber(cloneInstance.getRawValue(), false, false);
            number = this.decimalAdjust(number, this.roundUp());
            if (this.isItaNumber(number.toString())) {
                // TODO: investigate in the future how can this be avoided
                setTimeout(() => {
                    this.cleaveInstance.setRawValue(number.toString());
                    // this.writeValue(number.toString());
                }, 0);
            }
        }
    }
    onKeyDown(e) {
        // check if it's a single symbol and not Special like (Ctrl, backspace etc.)
        if (e?.key?.length <= 1 && !e.ctrlKey && !e.altKey && !e.metaKey) {
            // stop event propagation if key pressed is not a number or locale character or minus sign
            if (Number.isNaN(Number.parseInt(e.key, 10)) &&
                this.isMinusAllowed(e.key) &&
                e.key !== getDecimal(this.localeService?.currentLocale || this.locale_id)) {
                e.stopPropagation();
                e.preventDefault();
                // stop event propagation if key pressed exceeds the min/max value range
            }
            else if (!Number.isNaN(Number.parseInt(e.key, 10)) && this.isOutSideRange(e)) {
                e.stopPropagation();
                e.preventDefault();
            }
            else if (this.isOutSideRange(e)) {
                e.stopPropagation();
                e.preventDefault();
            }
        }
        // protect decimal symbol deletion in case fillFraction is on
        if (e.key === 'Delete' || e.key === 'Backspace') {
            const element = this._elementRef.nativeElement;
            // position of caret
            const pos = element.selectionStart;
            // get character about to be deleted
            const char = element.value.charAt(pos - 1);
            // in case the char is the decimal symbol prevent deletion and move caret in front
            if (this.fillFraction() &&
                char === getDecimal(this.localeService?.currentLocale || this.locale_id)) {
                e.stopPropagation();
                e.preventDefault();
                element.setSelectionRange(pos - 1, pos - 1);
            }
            else if (this.isOutSideRange(e)) {
                // don't allow insertion that will put number out of range
                e.stopPropagation();
                e.preventDefault();
            }
        }
    }
    onFocusout() {
        // in case fillFraction feature is on and there's no integer part add leading 0
        if (this.fillFraction() && this.cleaveInstance.getRawValue().match(/^\.[0-9]+/g)) {
            this.cleaveInstance.setRawValue('0' + this.cleaveInstance.getRawValue());
        }
        // in case round UP is enabled
        if (this.roundUp() > 0) {
            const number = this.parseNumber(this.cleaveInstance.getRawValue(), false, false);
            this.cleaveInstance.setRawValue(this.decimalAdjust(number, this.roundUp()).toString());
        }
        // in case min is set and value is less than min reset to the min value
        if (this.min() !== null && this.min() !== undefined && Number.parseFloat(this.cleaveInstance.getRawValue()) < this.min()) {
            this.cleaveInstance.setRawValue(this.min().toString());
        }
        // in case max is set and value is greater than max reset to the max value
        if (this.max() !== null && this.max() !== undefined && Number.parseFloat(this.cleaveInstance.getRawValue()) > this.max()) {
            this.cleaveInstance.setRawValue(this.max().toString());
        }
    }
    /**
     * @Override setPlaceholder setPlaceholderAttribute of InputDirective
     * Updates the placeholder value based on current number format and given value.
     * If value passed contains characters it doesn't format placeholder.
     * */
    setPlaceholderAttribute(value) {
        // if value is a number, format the value based on current number format
        if (typeof value === 'number' && !isNaN(value) && isPlatformBrowser(this.platformId)) {
            const div = this.document.createElement('input');
            // create a clone of cleaveInstance
            const cloneInstance = new Cleave(div, this.options);
            // BEWARE: by the time your setRawValue cleave options have changed thus number formatting
            cloneInstance.setRawValue(value.toString());
            // set the value with current format as placeholder
            super.setPlaceholderAttribute(cloneInstance.getFormattedValue());
        }
        else if (typeof value === 'string') {
            super.setPlaceholderAttribute(value.toString());
        }
        else if (isNaN(value)) {
            value = this._elementRef.nativeElement.placeholder;
            super.setPlaceholderAttribute(value.toString());
        }
    }
    /**
     * instantiate Cleave.js
     */
    initCleave(onChangeCallback) {
        if (isPlatformServer(this.platformId)) {
            // in case it's not a browser environment
            return;
        }
        // change the cleave prototype function to intercept onCut callback
        // and allow based on the event bubbling and disabled state
        // TIP: change behavior of onCut to stop in case bubble is canceled
        // TODO: See relevant issue https://github.com/nosir/cleave.js/issues/675
        const _onCut = Cleave.prototype['onCut'];
        Cleave.prototype['onCut'] = function (e) {
            /* eslint-enable no-invalid-this */
            // Check if the event is composed and stopPropagation hasn't been called
            if (!e.composed && !e.defaultPrevented && !this.element.disabled) {
                _onCut.call(this, e);
            }
            /* eslint-disable no-invalid-this */
        };
        // in case options is undefined
        const opts = { ...this.options };
        // set the onValue change listener to keep the value at readOnly element in sync
        opts.onValueChanged = onChangeCallback || this.onValueChanged.bind(this);
        // in case round UP is enabled
        if (this.roundUp() > 0) {
            const number = this.parseNumber(this._elementRef.nativeElement.value);
            this._elementRef.nativeElement.value = this.decimalAdjust(number, this.roundUp());
        }
        // create the Cleave instance with the new or updated options
        this.cleaveInstance = new Cleave(this._elementRef.nativeElement, opts);
    }
    /**
     * Updates the cleave options by destroying the current instance and creating another one while keeping the previous
     * value.
     */
    updateOptions(localeUpdated = false) {
        if (this.cleaveInstance) {
            // the value before destroying cleave instance
            const previousValue = this.cleaveInstance.getRawValue();
            // update the Cleave instance with the new or updated options
            this.cleaveInstance.properties = {
                ...this.cleaveInstance.properties,
                ...this.options,
                initValue: previousValue,
            };
            // init number formatter with updated options
            this.cleaveInstance.initNumeralFormatter();
            // In case previous settings where numeralDecimalMark='.' with value 1000.99, and they changed to
            // numeralDecimalMark=','; set initValue to 1000.99 or even 1000,99 will not be reflected and cleave will
            // remove the decimal part. Set of the value after initialization causes reformat of number and works.
            this.cleaveInstance.setRawValue(previousValue);
            // parse number
            const number = this.parseNumber(this._elementRef.nativeElement.placeholder || undefined, localeUpdated);
            // set placeholder in case none is provided
            this.setPlaceholderAttribute(number);
        }
    }
    /**
     * Callback function of the Cleave instance that is invoked when value is changed.
     *
     * @param event type of CleaveEventOnChange
     */
    onValueChanged(event) {
        const { target } = event;
        if (this.fillFraction()) {
            this.applyFractionFill(target.rawValue);
        }
        this.onChange(this.parseNumber(target.rawValue, false, false));
        // fill leading zero
        if (this.leadingZero()) {
            const size = this.leadingZero() > this.digits() && this.digits() > 0 ? this.digits() : this.leadingZero();
            this._elementRef.nativeElement.value = this.padZero(event.target.rawValue, size);
        }
    }
    /**
     * responsible to extract all locale information needed to build CleaveOptions related to number format.
     *
     * @param id The id of the locale
     */
    getCleaveOptsBasedOnLocale(id) {
        if (!id || id === '') {
            throw new Error('Locale id cannot be empty or undefined');
        }
        const opts = {
            numeral: true,
            delimiter: getGroup(id),
            numeralDecimalMark: getDecimal(id),
            numeralDecimalScale: this.fractionDigits(),
            numeralIntegerScale: this.digits(),
            numeralThousandsGroupStyle: this.noFormat() ? 'none' : 'thousand', // TODO: implement based on locale
        };
        return { ...this.options, ...opts };
    }
    /**
     * Applies the fill of decimal part with zeros in case decimal part of given number has
     * size less than the max fraction digits.
     *
     * @param number
     * @param cleaveInstance
     * @private
     */
    applyFractionFill(number, cleaveInstance = this.cleaveInstance) {
        // if fill fraction is enabled and there's cleave instance
        if (this.fillFraction()) {
            if (this.cleaveInstance) {
                number = number?.toString();
                if (number === '' || !number) {
                    return;
                }
                const hasDecimal = number.split('.').length > 1;
                // in case there's already decimal point in number
                if (hasDecimal) {
                    const decimalSize = number.split('.')[1].length;
                    // if size of decimal part of number is less than the max fraction digits
                    if (decimalSize < this.fractionDigits()) {
                        number = number + new Array(this.fractionDigits() - decimalSize).fill(0).join('');
                        const pos = this._elementRef.nativeElement.selectionStart;
                        cleaveInstance.setRawValue(number);
                        this._elementRef.nativeElement.setSelectionRange(pos, pos);
                    }
                    // in case there's not, force it by filling it as much zero as the fraction digits
                }
                else if (this.fractionDigits() > 0) {
                    number = `${number}.${new Array(this.fractionDigits()).fill(0).join('')}`;
                    const pos = this._elementRef.nativeElement.selectionStart;
                    cleaveInstance.setRawValue(number);
                    this._elementRef.nativeElement.setSelectionRange(pos, pos);
                }
            }
        }
    }
    /**
     * Parses a given formatted number and returns it as a number.
     *
     * @param value The formatted number you want to parse e.g. 111,888.22
     * @param userPrevLocaleState Use previous locale state to parse the value. By default, is false
     * @param replaceSymbol Replace the decimal symbol with a dot. By default, is false
     * @private
     */
    parseNumber(value, userPrevLocaleState = false, replaceSymbol = true) {
        // locale state
        const locale = userPrevLocaleState ? this.localeService?.previousLocale : this.localeService?.currentLocale || this.locale_id || 'en';
        // get decimal and group symbols
        const decimalSymbol = getDecimal(locale);
        const groupSymbol = getGroup(locale);
        // replace symbols to parse number
        const parsedNumber = value?.split(groupSymbol).join('').split(decimalSymbol).join('.');
        return this.parseBigNumber(replaceSymbol ? parsedNumber : value);
    }
    /**
     * Pad leading zero to the number
     *
     * @param num The number to add leading zero to
     * @param size Number of number's leading zero
     * @private
     */
    padZero(num, size) {
        num = num.toString();
        // get integer and decimal part
        // eslint-disable-next-line prefer-const
        let [integer, decimal] = num.split('.');
        while (integer.length < size) {
            integer = '0' + integer;
        }
        return decimal ? `${integer}.${decimal}` : integer;
    }
    /**
     * checks whether the min symbol is allowed. It is allowed when minus symbol
     * provided and min input is undefined or min provided and is negative.
     *
     * @param key The key string coming from an event
     */
    isMinusAllowed(key) {
        return !this.min() || this.min() < 0 ? key !== '-' : true;
    }
    /**
     * check whether the key entered is inside the min and
     * max range. Returns true if it's out of range.
     *
     * @param e A keyboard event
     */
    isOutSideRange(e) {
        if (!this.cleaveInstance) {
            return false;
        }
        // get caret position
        const pos = e.target['selectionStart'];
        // get caret position (in case there's text selected)
        const endPos = e.target['selectionEnd'];
        // get current value (formatted)
        let value = e.target['value'];
        // remove key from value
        if (e.key === 'Delete' || e.key === 'Backspace') {
            // check if there's selection of text to be removed
            if (pos !== endPos) {
                value = e.target['value'].substring(0, pos) + e.target['value'].substring(endPos, e.target['value'].length);
            }
            else {
                value = e.target['value'].substring(0, pos - 1) + e.target['value'].substring(endPos, e.target['value'].length);
            }
        }
        else {
            // add key to the value
            value = [value.slice(0, pos), e.key, value.slice(endPos)].join('');
        }
        // create a Cleave instance to extract the number from formatted value
        const cleave = new Cleave(this.document.createElement('input'), { ...this.options });
        cleave.setRawValue(value);
        const number = Number(cleave.getRawValue());
        // destroy cleave instance
        cleave.destroy();
        return !isNaN(number) &&
            this.min() >= 0 ? (this.isDefined(this.max()) && this.max() < number) : (this.min() > number || this.max() < number) &&
            this.max() <= 0 ? (this.isDefined(this.min()) && this.min() > number) : (this.min() > number || this.max() < number);
    }
    /**
     * Decimal adjustment of a number.
     *
     * @param value The number or string representation of a number.
     * @param exp   The exponent (the 10 logarithm of the adjustment base).
     * @returns The adjusted value.
     */
    decimalAdjust(value, exp) {
        // Ensure exp is a number
        exp = +Number(exp);
        // Convert input to string if it's a number
        const valueStr = typeof value === 'number' ? value.toString() : String(value);
        // Handle empty or invalid inputs
        if (!valueStr || isNaN(Number(valueStr))) {
            return '';
        }
        try {
            // Check if the value is negative
            const isNegative = valueStr.startsWith('-');
            // Get the absolute value string
            const absoluteValueStr = isNegative ? valueStr.substring(1) : valueStr;
            // Split the number into integer and decimal parts
            const parts = absoluteValueStr.split('.');
            let integerPart = parts[0] || '0'; // Default to '0' if empty
            let decimalPart = parts.length > 1 ? parts[1] : '';
            // If there's no decimal part and exp > 0, we should add zeros
            if (decimalPart === '' && exp > 0) {
                decimalPart = '0'.repeat(exp);
            }
            // Pad decimal part with zeros if needed
            if (decimalPart.length < exp) {
                decimalPart = decimalPart.padEnd(exp, '0');
            }
            // Determine if we need to round up
            let roundUp = false;
            if (decimalPart.length > exp) {
                const nextDigit = parseInt(decimalPart.charAt(exp), 10);
                roundUp = nextDigit >= 5;
            }
            // Trim the decimal part to the required precision
            decimalPart = decimalPart.substring(0, exp);
            // Handle rounding
            if (roundUp) {
                if (exp === 0) {
                    // If exp is 0, we need to round the integer part
                    integerPart = (BigInt(integerPart) + 1n).toString();
                }
                else {
                    // Create a number from the decimal part
                    let decimalNum = parseInt(decimalPart, 10);
                    // Add 1 to the decimal part
                    decimalNum += 1;
                    // Check if we need to carry over to the integer part
                    if (decimalNum.toString().length > exp) {
                        integerPart = (BigInt(integerPart) + 1n).toString();
                        decimalNum = 0;
                    }
                    // Convert back to string and pad with leading zeros if needed
                    decimalPart = decimalNum.toString().padStart(exp, '0');
                }
            }
            // Combine the parts
            let result;
            if (exp > 0) {
                // Remove trailing zeros if they're not significant
                let trimmedDecimalPart = decimalPart;
                while (trimmedDecimalPart.length > 0 && trimmedDecimalPart.charAt(trimmedDecimalPart.length - 1) === '0') {
                    trimmedDecimalPart = trimmedDecimalPart.substring(0, trimmedDecimalPart.length - 1);
                }
                result = trimmedDecimalPart.length > 0 ? `${integerPart}.${trimmedDecimalPart}` : integerPart;
            }
            else {
                result = integerPart;
            }
            // Restore the negative sign if needed
            return isNegative ? '-' + result : result;
        }
        catch (error) {
            console.error('Error in decimalAdjust:', error);
            // If there's an error, return a formatted original value or '0'
            return valueStr || '0';
        }
    }
    /**
     * Checks whether a value is defined or not
     *
     * @param value
     */
    isDefined(value) {
        return value !== undefined && value !== null;
    }
    /**
     * Check if the value is a valid number. Take into account the Big numbers
     * more than 16 digits. Check if string contains only digits and dots.
     * The number value must not be formatted.
     * @param value the value to check
     * @private
     */
    isItaNumber(value) {
        const length = value?.split('.')[0].length;
        return length > 15 ? /^\d+(\.\d+)?$/.test(value) : !isNaN(Number(value));
    }
    /**
     * Parse the value to a number. For large numbers, the value is a string.
     * The number value must not be formatted.
     * @param value the value to parse
     * @private
     */
    parseBigNumber(value) {
        // for large numbers, the value is a string
        const length = value?.split('.')[0].length;
        return length > 15 ? value : Number.parseFloat(value);
    }
    /**
     * @description
     * Transforms the invalid state input to a boolean value
     *
     * @param state
     * @private
     */
    invalidTransform(state) {
        // in case it's controlled by NgControl override
        const value = coerceBooleanProperty(state);
        // set BaseDirective Attribute
        this.euiDanger = value;
        return value;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: EuiInputNumberComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.1", type: EuiInputNumberComponent, isStandalone: true, selector: "input[euiInputNumber]", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, leadingZero: { classPropertyName: "leadingZero", publicName: "leadingZero", isSignal: true, isRequired: false, transformFunction: null }, isInvalid: { classPropertyName: "isInvalid", publicName: "isInvalid", isSignal: true, isRequired: false, transformFunction: null }, fractionDigits: { classPropertyName: "fractionDigits", publicName: "fractionDigits", isSignal: true, isRequired: false, transformFunction: null }, digits: { classPropertyName: "digits", publicName: "digits", isSignal: true, isRequired: false, transformFunction: null }, fillFraction: { classPropertyName: "fillFraction", publicName: "fillFraction", isSignal: true, isRequired: false, transformFunction: null }, roundUp: { classPropertyName: "roundUp", publicName: "roundUp", isSignal: true, isRequired: false, transformFunction: null }, noFormat: { classPropertyName: "noFormat", publicName: "noFormat", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "blur": "onTouched()", "paste": "onPaste($event)", "keydown": "onKeyDown($event)", "focusout": "onFocusout()" }, properties: { "attr.type": "type", "class": "this.cssClasses" } }, usesInheritance: true, usesOnChanges: true, hostDirectives: [{ directive: i1.EuiClearableDirective, inputs: ["euiClearable", "euiClearable", "readonly", "readonly", "disabled", "disabled"] }, { directive: i1.EuiLoadingDirective, inputs: ["euiLoading", "euiLoading", "readonly", "readonly"] }], ngImport: i0, template: '', isInline: true, styles: [".eui-21 :host.eui-input-number{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:var(--eui-f);appearance:none;background-color:var(--eui-c-surface-container);border:1px solid var(--eui-c-secondary-border);border-radius:var(--eui-br-s);box-shadow:none;color:var(--eui-c-text);min-height:var(--eui-s-5xl);padding:calc(var(--eui-s-xs) - 1px) calc(var(--eui-s-m) - 1px);width:100%}.eui-21 :host.eui-input-number:focus:not([readonly]){outline:2px solid var(--eui-c-focus)!important;outline-offset:-2px!important;transition:none}.eui-21 :host.eui-input-number:focus-visible:not([readonly]){outline:2px solid var(--eui-c-focus-visible)!important;outline-offset:-2px!important;transition:none}.eui-21 :host.eui-input-number [tabindex=\"0\"]:focus:not([readonly]){outline:2px solid var(--eui-c-focus-visible)!important;outline-offset:-2px!important;transition:none}.eui-21 :host.eui-input-number:not([hidden]){display:block}.eui-21 :host.eui-input-number::placeholder{color:var(--eui-c-text-lighter)!important;opacity:1}.eui-21 :host.eui-input-number[readonly]{background-color:transparent;border-color:transparent;box-shadow:none;height:inherit;padding-left:0;padding-right:0;pointer-events:none}.eui-21 :host.eui-input-number[readonly]+.eui-feedback-message,.eui-21 :host.eui-input-number[readonly]+.eui-input-maxlength{appearance:none;display:none}.eui-21 :host.eui-input-number[disabled]:not([readonly]),.eui-21 :host.eui-input-number--disabled:not([readonly]){background-color:var(--eui-c-disabled-bg)!important;border:1px solid var(--eui-c-secondary-border-lighter);color:var(--eui-c-text-light);pointer-events:none}.eui-21 :host.eui-input-number--invalid,.eui-21 :host.eui-input-number--danger{border:1px solid var(--eui-c-danger-border)}.eui-21 :host.eui-input-number--loading{padding-right:var(--eui-s-7xl)}.eui-21 :host.eui-input-number--loading-icon{align-items:center;display:flex}.eui-21 :host.eui-input-number--loading-icon:after,.eui-21 :host.eui-input-number--loading-icon:before{content:\"\";height:var(--eui-s-l);position:absolute;right:var(--eui-s-s);width:var(--eui-s-l)}.eui-21 :host.eui-input-number--loading-icon:before{border:var(--eui-s-3xs) solid rgba(0,0,0,.35);border-radius:100%}.eui-21 :host.eui-input-number--loading-icon:after{animation:loading .6s linear;animation-iteration-count:infinite;border-color:var(--eui-c-white) transparent transparent;border-radius:100%;border-style:solid;border-width:var(--eui-s-3xs);box-shadow:0 0 0 1px transparent}.eui-21 :host.eui-input-number--loading-icon--sm:after,.eui-21 :host.eui-input-number--loading-icon--sm:before{height:var(--eui-s-m);width:var(--eui-s-m)}@keyframes loading{to{transform:rotate(360deg)}}.eui-21 :host.eui-input-number--clearable{padding-right:var(--eui-s-7xl)}.eui-21 :host.eui-input-number[readonly]{pointer-events:auto}\n"] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: EuiInputNumberComponent, decorators: [{
            type: Component,
            args: [{ selector: 'input[euiInputNumber]', template: '', hostDirectives: [
                        {
                            directive: EuiClearableDirective,
                            inputs: ['euiClearable', 'readonly', 'disabled'],
                        },
                        {
                            directive: EuiLoadingDirective,
                            inputs: ['euiLoading', 'readonly'],
                        },
                    ], host: {
                        '(blur)': 'onTouched()',
                        '[attr.type]': 'type',
                        '(paste)': 'onPaste($event)',
                    }, styles: [".eui-21 :host.eui-input-number{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:var(--eui-f);appearance:none;background-color:var(--eui-c-surface-container);border:1px solid var(--eui-c-secondary-border);border-radius:var(--eui-br-s);box-shadow:none;color:var(--eui-c-text);min-height:var(--eui-s-5xl);padding:calc(var(--eui-s-xs) - 1px) calc(var(--eui-s-m) - 1px);width:100%}.eui-21 :host.eui-input-number:focus:not([readonly]){outline:2px solid var(--eui-c-focus)!important;outline-offset:-2px!important;transition:none}.eui-21 :host.eui-input-number:focus-visible:not([readonly]){outline:2px solid var(--eui-c-focus-visible)!important;outline-offset:-2px!important;transition:none}.eui-21 :host.eui-input-number [tabindex=\"0\"]:focus:not([readonly]){outline:2px solid var(--eui-c-focus-visible)!important;outline-offset:-2px!important;transition:none}.eui-21 :host.eui-input-number:not([hidden]){display:block}.eui-21 :host.eui-input-number::placeholder{color:var(--eui-c-text-lighter)!important;opacity:1}.eui-21 :host.eui-input-number[readonly]{background-color:transparent;border-color:transparent;box-shadow:none;height:inherit;padding-left:0;padding-right:0;pointer-events:none}.eui-21 :host.eui-input-number[readonly]+.eui-feedback-message,.eui-21 :host.eui-input-number[readonly]+.eui-input-maxlength{appearance:none;display:none}.eui-21 :host.eui-input-number[disabled]:not([readonly]),.eui-21 :host.eui-input-number--disabled:not([readonly]){background-color:var(--eui-c-disabled-bg)!important;border:1px solid var(--eui-c-secondary-border-lighter);color:var(--eui-c-text-light);pointer-events:none}.eui-21 :host.eui-input-number--invalid,.eui-21 :host.eui-input-number--danger{border:1px solid var(--eui-c-danger-border)}.eui-21 :host.eui-input-number--loading{padding-right:var(--eui-s-7xl)}.eui-21 :host.eui-input-number--loading-icon{align-items:center;display:flex}.eui-21 :host.eui-input-number--loading-icon:after,.eui-21 :host.eui-input-number--loading-icon:before{content:\"\";height:var(--eui-s-l);position:absolute;right:var(--eui-s-s);width:var(--eui-s-l)}.eui-21 :host.eui-input-number--loading-icon:before{border:var(--eui-s-3xs) solid rgba(0,0,0,.35);border-radius:100%}.eui-21 :host.eui-input-number--loading-icon:after{animation:loading .6s linear;animation-iteration-count:infinite;border-color:var(--eui-c-white) transparent transparent;border-radius:100%;border-style:solid;border-width:var(--eui-s-3xs);box-shadow:0 0 0 1px transparent}.eui-21 :host.eui-input-number--loading-icon--sm:after,.eui-21 :host.eui-input-number--loading-icon--sm:before{height:var(--eui-s-m);width:var(--eui-s-m)}@keyframes loading{to{transform:rotate(360deg)}}.eui-21 :host.eui-input-number--clearable{padding-right:var(--eui-s-7xl)}.eui-21 :host.eui-input-number[readonly]{pointer-events:auto}\n"] }]
        }], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], leadingZero: [{ type: i0.Input, args: [{ isSignal: true, alias: "leadingZero", required: false }] }], isInvalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "isInvalid", required: false }] }], cssClasses: [{
                type: HostBinding,
                args: ['class']
            }], fractionDigits: [{ type: i0.Input, args: [{ isSignal: true, alias: "fractionDigits", required: false }] }], digits: [{ type: i0.Input, args: [{ isSignal: true, alias: "digits", required: false }] }], fillFraction: [{ type: i0.Input, args: [{ isSignal: true, alias: "fillFraction", required: false }] }], roundUp: [{ type: i0.Input, args: [{ isSignal: true, alias: "roundUp", required: false }] }], noFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "noFormat", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], onKeyDown: [{
                type: HostListener,
                args: ['keydown', ['$event']]
            }], onFocusout: [{
                type: HostListener,
                args: ['focusout']
            }] } });

/**
 * @description
 * Directive for the {@link EuiInputNumberComponent}. This directive is used to
 * handle the value changes of the input element and update the value of the
 * directive and the form control. It also implements the ControlValueAccessor
 * interface to allow the directive to be used with reactive forms.
 *
 * @usageNotes
 * ### With Reactive Forms
 * ```typescript
 * form = new FormGroup({
 *   price: new FormControl(0)
 * });
 * ```
 *
 * ```html
 * <input euiInputNumber formControlName="price" [fractionDigits]="2" />
 * ```
 *
 * ### Accessibility
 * - Integrates with Angular Forms validation
 * - Automatically handles disabled state
 *
 * ### Notes
 * - Automatically applied when using formControl, formControlName, or ngModel
 * - Handles large number precision by returning strings when needed
 */
class EuiInputNumberDirective {
    constructor() {
        this.wasIncomplete = false;
        this.elementRef = inject(ElementRef);
        this.euiNumberComponent = inject(EuiInputNumberComponent, { optional: true });
        this.document = inject(DOCUMENT);
        this.platformId = inject(PLATFORM_ID);
    }
    ngOnInit() {
        this.euiNumberComponent['initCleave'](this.valueChanges.bind(this));
    }
    // TODO: find the correct type or turn into a generic, https://www.typescriptlang.org/docs/handbook/2/generics.html
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    writeValue(obj) {
        if (isPlatformServer(this.platformId)) {
            return;
        }
        const cleave = new Cleave(this.document.createElement('input'), { ...this.euiNumberComponent['options'] });
        cleave.setRawValue(obj);
        if (this.euiNumberComponent.fillFraction() && obj !== '' && obj) {
            this.euiNumberComponent['applyFractionFill'](obj, cleave);
        }
        let valueOfElement;
        // fill leading zero
        if (this.euiNumberComponent.leadingZero()) {
            const size = this.euiNumberComponent.leadingZero() > this.euiNumberComponent.digits() && this.euiNumberComponent.digits() > 0
                ? this.euiNumberComponent.digits()
                : this.euiNumberComponent.leadingZero();
            valueOfElement = this.euiNumberComponent['padZero'](cleave.getFormattedValue(), size);
        }
        else {
            valueOfElement = cleave.getFormattedValue();
        }
        this.elementRef.nativeElement.value = valueOfElement;
        const { canConvert } = this.canSafelyConvertToNumber(cleave.getRawValue());
        this.value = !canConvert ? cleave.getRawValue() : Number.parseFloat(cleave.getRawValue());
        cleave.destroy();
    }
    // TODO: find the correct type or turn into a generic, https://www.typescriptlang.org/docs/handbook/2/generics.html
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    registerOnChange(fn) {
        this.onChange = (t) => {
            this.value = t;
            fn(t);
        };
    }
    // TODO: find the correct type or turn into a generic, https://www.typescriptlang.org/docs/handbook/2/generics.html
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    registerOnTouched(fn) {
        this.onTouched = fn;
    }
    /**
     * Set the disabled state of the input element.
     *
     * @param isDisabled
     */
    setDisabledState(isDisabled) {
        this.euiNumberComponent.disabled = isDisabled;
    }
    validate(control) {
        const raw = this.elementRef.nativeElement.value;
        return raw !== '' && !/\d/.test(raw) ? { incomplete: true } : null;
    }
    registerOnValidatorChange(fn) {
        this.onValidatorChange = fn;
    }
    onTouched() {
        // this.euiNumberComponent.isInvalid = this.control.invalid;
    }
    /**
     * Handle the value changes of the input element and update the value of the
     * directive and the form control. For large numbers, the value is a string.
     *
     * @param event
     */
    valueChanges(event) {
        const { target } = event;
        this.euiNumberComponent['onValueChanged'](event);
        // Skip intermediate/incomplete values that contain no digit yet
        const isIncomplete = target.rawValue !== '' && !/\d/.test(target.rawValue);
        if (isIncomplete !== this.wasIncomplete) {
            this.wasIncomplete = isIncomplete;
            this.onValidatorChange?.();
        }
        if (isIncomplete) {
            return;
        }
        const { canConvert, value } = this.canSafelyConvertToNumber(target.rawValue);
        // Only emit change if the value has actually changed
        if (this.value !== target.rawValue && this.onChange) {
            this.onChange(!canConvert ? target.rawValue : value);
        }
    }
    /**
     * Check if a numeric string can be safely converted to a number without
     * losing precision or exceeding JavaScript's maximum safe integer limit.
     * This is important for very large numbers that may be represented
     * as strings to avoid precision loss.
     *
     * @param numString The numeric string to check
     * @return An object indicating if conversion is safe, the reason if not, and the converted value if safe
     */
    canSafelyConvertToNumber(numString) {
        // Remove whitespace
        const trimmed = numString.trim();
        if (trimmed === '') {
            return {
                canConvert: false,
                reason: 'Empty string converts to null',
                value: null,
            };
        }
        // Convert to number
        const numValue = Number(trimmed);
        // Check if it's infinity (exceeds MAX_VALUE)
        if (!isFinite(numValue)) {
            return {
                canConvert: false,
                reason: 'Number exceeds maximum value',
                value: null,
            };
        }
        // Convert back to string
        const convertedBack = numValue.toString();
        // Normalize BOTH strings without converting to number
        const normalizedOriginal = this.normalizeStringOnly(trimmed);
        const normalizedConverted = this.normalizeStringOnly(convertedBack);
        if (normalizedOriginal !== normalizedConverted) {
            return {
                canConvert: false,
                reason: 'Precision loss detected',
                value: null,
                original: trimmed,
                converted: numValue,
            };
        }
        return {
            canConvert: true,
            reason: 'Safe to convert',
            value: numValue,
        };
    }
    /**
     * Normalize a numeric string by removing leading/trailing zeros and signs,
     * without converting to a number. This helps compare the original and converted
     * strings for equivalence.
     *
     * @param str The numeric string to normalize
     * @returns The normalized string
     */
    normalizeStringOnly(str) {
        let s = str.trim();
        // Remove leading + sign
        if (s.startsWith('+')) {
            s = s.substring(1);
        }
        // Handle decimal numbers
        if (s.includes('.')) {
            let [intPart, decPart] = s.split('.');
            // Remove leading zeros from integer part (but keep at least one zero)
            intPart = intPart.replace(/^0+/, '') || '0';
            // Remove trailing zeros from decimal part
            decPart = decPart.replace(/0+$/, '');
            // If no decimal digits left, return just integer
            if (decPart === '') {
                return intPart;
            }
            return intPart + '.' + decPart;
        }
        else {
            // Integer - remove leading zeros
            return s.replace(/^0+/, '') || '0';
        }
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: EuiInputNumberDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.1", type: EuiInputNumberDirective, isStandalone: true, selector: "input[euiInputNumber][formControl],input[euiInputNumber][formControlName],input[euiInputNumber][ngModel]", providers: [
            {
                provide: NG_VALUE_ACCESSOR,
                useExisting: forwardRef(() => EuiInputNumberDirective),
                multi: true,
            },
            {
                provide: NG_VALIDATORS,
                useExisting: forwardRef(() => EuiInputNumberDirective),
                multi: true,
            },
        ], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: EuiInputNumberDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: 'input[euiInputNumber][formControl],input[euiInputNumber][formControlName],input[euiInputNumber][ngModel]',
                    providers: [
                        {
                            provide: NG_VALUE_ACCESSOR,
                            useExisting: forwardRef(() => EuiInputNumberDirective),
                            multi: true,
                        },
                        {
                            provide: NG_VALIDATORS,
                            useExisting: forwardRef(() => EuiInputNumberDirective),
                            multi: true,
                        },
                    ],
                }]
        }] });

const EUI_INPUT_NUMBER = [
    EuiInputNumberComponent,
    EuiInputNumberDirective,
];

/**
 * Generated bundle index. Do not edit.
 */

export { EUI_INPUT_NUMBER, EuiInputNumberComponent, EuiInputNumberDirective };
//# sourceMappingURL=eui-components-eui-input-number.mjs.map
