import * as i0 from '@angular/core';
import { input, inject, ElementRef, Renderer2, effect, forwardRef, Directive, NgModule } from '@angular/core';
import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
import { LocaleService } from '@seniorsistemas/angular-components/locale';
import BigNumber from 'bignumber.js';

/**
 * Numeric mask directive with internationalization support.
 *
 * Formats numeric values according to the specified locale, applying
 * appropriate thousand and decimal separators, with support for negative values,
 * min/max decimal places control, and scientific notation.
 *
 * @example
 * ```html
 * <input
 *   type="text"
 *   sNumericMask
 *   [locale]="'pt-BR'"
 *   [minDecimalPlaces]="2"
 *   [maxDecimalPlaces]="2"
 *   [allowNegative]="true"
 *   [(ngModel)]="value"
 * />
 * ```
 */
class NumericMaskDirective {
    /**
     * Locale for formatting (e.g. 'pt-BR', 'en-US', 'de-DE')
     * If not provided, uses the locale from LocaleService
     */
    locale = input(undefined);
    /**
     * Minimum number of decimal places to display
     * @default 0
     */
    minDecimalPlaces = input(0);
    /**
     * Maximum number of decimal places allowed
     * @default 10
     */
    maxDecimalPlaces = input(10);
    /**
     * Allows negative values
     * @default false
     */
    allowNegative = input(false);
    /**
     * Enables scientific notation support
     * @default true
     */
    allowScientificNotation = input(true);
    /**
     * Text alignment in input
     * @default 'right'
     */
    textAlign = input('right');
    /**
     * Minimum value allowed
     */
    min = input(undefined);
    /**
     * Maximum value allowed
     */
    max = input(undefined);
    elementRef = inject((ElementRef));
    renderer = inject(Renderer2);
    localeService = inject(LocaleService, { optional: true });
    onChange = () => { };
    onTouched = () => { };
    decimalSeparator = ',';
    thousandSeparator = '.';
    currentLocale = 'pt-BR';
    lastModelValue = null;
    constructor() {
        effect(() => {
            const inputLocale = this.locale();
            if (inputLocale) {
                this.currentLocale = inputLocale;
                this.updateSeparators();
            }
        });
    }
    ngOnInit() {
        if (this.minDecimalPlaces() > this.maxDecimalPlaces()) {
            throw new Error(`NumericMaskDirective: minDecimalPlaces (${this.minDecimalPlaces()}) cannot be greater than maxDecimalPlaces (${this.maxDecimalPlaces()})`);
        }
        if (this.locale()) {
            this.currentLocale = this.locale();
            this.updateSeparators();
            this.setInputMode();
        }
        else if (this.localeService) {
            this.localeService.get().subscribe(() => {
                this.currentLocale = this.localeService.getLocaleOptions()?.locale || 'pt-BR';
                this.updateSeparators();
                this.setInputMode();
            });
        }
        else {
            this.updateSeparators();
            this.setInputMode();
        }
    }
    /**
     * Listener for input events (typing)
     */
    onInput(event) {
        const input = event.target;
        let value = input.value;
        value = this.normalizeSeparators(value);
        this.processInputValue(value);
    }
    /**
     * Listener for focus events (gaining focus)
     */
    onFocus() {
        const input = this.elementRef.nativeElement;
        const currentValue = input.value;
        if (!currentValue || currentValue.trim() === '') {
            return;
        }
        if (this.lastModelValue) {
            const displayValue = this.lastModelValue.replace('.', this.decimalSeparator);
            this.renderer.setProperty(input, 'value', displayValue);
            input.select();
            return;
        }
        const modelValue = this.toModelValue(currentValue);
        if (!modelValue) {
            return;
        }
        const displayValue = modelValue.replace('.', this.decimalSeparator);
        this.renderer.setProperty(input, 'value', displayValue);
        input.select();
    }
    /**
     * Listener for blur events (focus loss)
     */
    onBlur() {
        this.onTouched();
        const currentValue = this.elementRef.nativeElement.value;
        if (!currentValue || currentValue.trim() === '') {
            this.onChange(null);
            this.lastModelValue = null;
            return;
        }
        const modelValue = this.toModelValue(currentValue);
        if (!modelValue) {
            this.onChange(null);
            this.lastModelValue = null;
            return;
        }
        this.lastModelValue = modelValue;
        const displayValue = this.fromModelValue(modelValue);
        this.renderer.setProperty(this.elementRef.nativeElement, 'value', displayValue);
        this.onChange(modelValue);
    }
    /**
     * Listener for paste events (clipboard)
     */
    onPaste(event) {
        event.preventDefault();
        const pastedText = event.clipboardData?.getData('text') || '';
        let value = this.parseClipboardValue(pastedText);
        this.processInputValue(value);
    }
    /**
     * Listener for special keys (+ and -)
     */
    onKeyDown(event) {
        const key = event.key;
        if (key === '-' || key === '+') {
            const input = this.elementRef.nativeElement;
            const value = input.value;
            const cursorPosition = input.selectionStart || 0;
            if (key === '-' && this.allowScientificNotation()) {
                const charBeforeCursor = value[cursorPosition - 1];
                if (charBeforeCursor === 'e' || charBeforeCursor === 'E') {
                    return;
                }
            }
            event.preventDefault();
            if (!this.allowNegative()) {
                return;
            }
            this.toggleSign(value, key === '-');
        }
    }
    /**
     * Listener for keypress to block invalid characters
     */
    onKeyPress(event) {
        const key = event.key;
        const input = this.elementRef.nativeElement;
        const cursorPosition = input.selectionStart || 0;
        const value = input.value;
        if ((key === '-' || key === '+') && this.allowScientificNotation()) {
            const charBeforeCursor = value[cursorPosition - 1];
            if (charBeforeCursor === 'e' || charBeforeCursor === 'E') {
                return;
            }
        }
        if (key === '+' || key === '-') {
            event.preventDefault();
            return;
        }
        const isDigit = /^[0-9]$/.test(key);
        const isLocaleDecimalSeparator = key === this.decimalSeparator;
        const isOtherSeparator = (key === ',' || key === '.') && key !== this.decimalSeparator;
        const isScientificNotation = (key === 'e' || key === 'E') && this.allowScientificNotation();
        const isControlKey = event.ctrlKey || event.metaKey || event.altKey;
        const isSpecialKey = [
            'Backspace',
            'Tab',
            'Enter',
            'Escape',
            'ArrowLeft',
            'ArrowRight',
            'Delete',
            'Home',
            'End',
        ].includes(key);
        if (isOtherSeparator) {
            event.preventDefault();
            return;
        }
        if (isScientificNotation) {
            if (value.includes('e') || value.includes('E')) {
                event.preventDefault();
                return;
            }
            const charBeforeCursor = value[cursorPosition - 1];
            if (!charBeforeCursor || !/^[0-9]$/.test(charBeforeCursor)) {
                event.preventDefault();
                return;
            }
        }
        if (!isDigit && !isLocaleDecimalSeparator && !isScientificNotation && !isControlKey && !isSpecialKey) {
            event.preventDefault();
        }
    }
    /**
     * Updates separators according to the locale
     * @private
     */
    updateSeparators() {
        const formatted = new Intl.NumberFormat(this.currentLocale).format(1234.5);
        this.thousandSeparator = formatted[1];
        this.decimalSeparator = formatted[5];
    }
    /**
     * Processes input value and updates the input and model
     * @private
     */
    processInputValue(value) {
        if (!this.decimalSeparator || !this.thousandSeparator) {
            this.updateSeparators();
        }
        this.renderer.setProperty(this.elementRef.nativeElement, 'value', value);
        const modelValue = this.toModelValue(value);
        // During typing, clear the stored model value so onFocus will recalculate
        this.lastModelValue = null;
        this.onChange(modelValue);
    }
    /**
     * Sets inputmode for numeric keyboard on mobile devices
     * @private
     */
    setInputMode() {
        this.renderer.setAttribute(this.elementRef.nativeElement, 'inputmode', 'decimal');
        this.renderer.setAttribute(this.elementRef.nativeElement, 'autocomplete', 'off');
    }
    /**
     * Removes separators from a formatted value
     * @private
     */
    removeSeparators(value) {
        const thousandRegex = new RegExp(`\\${this.escapeRegex(this.thousandSeparator)}`, 'g');
        return value.replace(thousandRegex, '');
    }
    /**
     * Strips unnecessary trailing zeros from decimal part
     * @private
     */
    stripTrailingZeros(value) {
        const parts = value.split(this.decimalSeparator);
        if (parts.length === 2) {
            const decimalPart = parts[1].replace(/0+$/, '');
            return decimalPart.length > 0 ? parts[0] + this.decimalSeparator + decimalPart : parts[0];
        }
        return value;
    }
    /**
     * Normalizes separators during typing
     * @private
     */
    normalizeSeparators(value) {
        value = this.removeSeparators(value);
        if (this.decimalSeparator === ',') {
            value = value.replace(/\./g, ',');
        }
        else if (this.decimalSeparator === '.') {
            value = value.replace(/,/g, '.');
        }
        const parts = value.split(this.decimalSeparator);
        if (parts.length > 2) {
            value = parts[0] + this.decimalSeparator + parts.slice(1).join('');
        }
        return value;
    }
    /**
     * Escapes special characters for use in regex
     * @private
     */
    escapeRegex(str) {
        return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    }
    /**
     * Truncates a number to the specified decimal places (never rounds)
     * @private
     */
    truncateToDecimalPlaces(bigNumber, decimalPlaces) {
        if (decimalPlaces < 0) {
            return bigNumber;
        }
        const multiplier = new BigNumber(10).pow(decimalPlaces);
        return bigNumber.multipliedBy(multiplier).integerValue(BigNumber.ROUND_DOWN).dividedBy(multiplier);
    }
    /**
     * Checks if the value has excessive decimal places
     * @private
     */
    hasExcessiveDecimalPlaces(value) {
        const parts = value.split(this.decimalSeparator);
        if (parts.length > 1) {
            const decimalPart = parts[1].replace(/[^0-9]/g, '');
            return decimalPart.length > this.maxDecimalPlaces();
        }
        return false;
    }
    /**
     * Clears the input and notifies the model
     * @private
     */
    clearInput() {
        this.renderer.setProperty(this.elementRef.nativeElement, 'value', '');
        this.lastModelValue = null;
        this.onChange(null);
    }
    /**
     * Parses a string value to its numeric representation, handling separators and sign
     * Returns BigNumber to preserve full precision and optional number conversion
     * @private
     */
    parseStringToValue(value) {
        const isNegative = value.startsWith('-');
        let numericStr = value
            .replace(/^-/, '')
            .replace(new RegExp(`\\${this.thousandSeparator}`, 'g'), '')
            .replace(new RegExp(`\\${this.decimalSeparator}`, 'g'), '.');
        if (numericStr === '.' || numericStr === '') {
            numericStr = '0';
        }
        else if (numericStr.startsWith('.')) {
            numericStr = '0' + numericStr;
        }
        let bigNumber = new BigNumber(numericStr);
        if (bigNumber.isNaN()) {
            bigNumber = new BigNumber(0);
        }
        if (isNegative && this.allowNegative()) {
            bigNumber = bigNumber.negated();
        }
        return { bigNumber, numeric: bigNumber.toNumber(), isNegative };
    }
    /**
     * Formats a numeric value according to the locale
     * Truncates (never rounds) to maxDecimalPlaces for display purposes only
     * @private
     */
    formatNumeric(numeric) {
        let bigNumeric = numeric instanceof BigNumber ? numeric : new BigNumber(numeric);
        const truncatedValue = this.truncateToDecimalPlaces(bigNumeric, this.maxDecimalPlaces());
        const valueStr = truncatedValue.toFixed(this.maxDecimalPlaces());
        let formattedValue = valueStr.replace('.', this.decimalSeparator);
        const parts = formattedValue.split(this.decimalSeparator);
        const integerPart = parts[0];
        const decimalPart = parts[1] || '';
        const formattedInteger = new Intl.NumberFormat(this.currentLocale).format(parseInt(integerPart, 10) || 0).split(this.decimalSeparator)[0];
        const minDecimal = Math.max(this.minDecimalPlaces(), decimalPart.replace(/0+$/, '').length || 0);
        const finalDecimal = decimalPart.padEnd(minDecimal, '0');
        return formattedInteger + this.decimalSeparator + finalDecimal;
    }
    /**
     * Formats the value according to the locale
     * @private
     */
    formatValue(value) {
        if (!value || value.trim() === '') {
            return '';
        }
        const { numeric } = this.parseStringToValue(value);
        return this.formatNumeric(numeric);
    }
    /**
     * Converts the formatted value to the model format (international standard string)
     * Always returns decimal notation, never scientific notation
     * Stores the full precision value without truncation
     * @private
     */
    toModelValue(formattedValue) {
        if (!formattedValue || formattedValue.trim() === '') {
            return null;
        }
        const { bigNumber } = this.parseStringToValue(formattedValue);
        return bigNumber.toFixed();
    }
    /**
     * Converts the model value to display format
     * @private
     */
    fromModelValue(modelValue) {
        if (modelValue === null || modelValue === undefined || modelValue === '') {
            return '';
        }
        const bigNumber = new BigNumber(modelValue);
        if (bigNumber.isNaN()) {
            return '';
        }
        return this.formatNumeric(bigNumber);
    }
    /**
     * Processes pasted value from clipboard
     * Converts scientific notation to decimal using BigNumber
     * @private
     */
    parseClipboardValue(text) {
        if (this.allowScientificNotation() && /[eE]/.test(text)) {
            try {
                const bigNumeric = new BigNumber(text);
                if (!bigNumeric.isNaN()) {
                    text = bigNumeric.toFixed();
                }
            }
            catch {
                void 0;
            }
        }
        return this.normalizeSeparators(text);
    }
    /**
     * Toggles the sign (- or +) of a value
     * @private
     */
    toggleSign(value, makeNegative) {
        const input = this.elementRef.nativeElement;
        let newValue = value;
        if (makeNegative && !newValue.startsWith('-')) {
            newValue = '-' + newValue;
        }
        else if (!makeNegative && newValue.startsWith('-')) {
            newValue = newValue.substring(1);
        }
        else {
            return;
        }
        this.renderer.setProperty(input, 'value', newValue);
        const modelValue = this.toModelValue(newValue);
        this.lastModelValue = null;
        this.onChange(modelValue);
    }
    writeValue(value) {
        if (!this.decimalSeparator || !this.thousandSeparator) {
            this.updateSeparators();
        }
        const hasFocus = document.activeElement === this.elementRef.nativeElement;
        if (hasFocus) {
            const formattedValue = this.fromModelValue(value);
            const rawValue = this.stripTrailingZeros(this.removeSeparators(formattedValue));
            this.renderer.setProperty(this.elementRef.nativeElement, 'value', rawValue);
        }
        else {
            const formattedValue = this.fromModelValue(value);
            this.renderer.setProperty(this.elementRef.nativeElement, 'value', formattedValue);
        }
    }
    registerOnChange(fn) {
        this.onChange = fn;
    }
    registerOnTouched(fn) {
        this.onTouched = fn;
    }
    setDisabledState(isDisabled) {
        this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', isDisabled);
    }
    validate(control) {
        const value = control.value;
        if (!value) {
            return null;
        }
        const numericValue = parseFloat(value);
        if (isNaN(numericValue)) {
            return null;
        }
        if (!this.allowNegative() && numericValue < 0) {
            return { negativeNotAllowed: true };
        }
        const minValue = this.min();
        if (minValue !== undefined && numericValue < minValue) {
            return {
                min: {
                    min: minValue,
                    actual: numericValue,
                },
            };
        }
        const maxValue = this.max();
        if (maxValue !== undefined && numericValue > maxValue) {
            return {
                max: {
                    max: maxValue,
                    actual: numericValue,
                },
            };
        }
        const decimalPart = value.split('.')[1];
        if (decimalPart && decimalPart.length > this.maxDecimalPlaces()) {
            return {
                excessiveDecimalPlaces: {
                    max: this.maxDecimalPlaces(),
                    actual: decimalPart.length,
                },
            };
        }
        return null;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NumericMaskDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
    static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.2.14", type: NumericMaskDirective, isStandalone: true, selector: "[sNumericMask]", inputs: { locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, minDecimalPlaces: { classPropertyName: "minDecimalPlaces", publicName: "minDecimalPlaces", isSignal: true, isRequired: false, transformFunction: null }, maxDecimalPlaces: { classPropertyName: "maxDecimalPlaces", publicName: "maxDecimalPlaces", isSignal: true, isRequired: false, transformFunction: null }, allowNegative: { classPropertyName: "allowNegative", publicName: "allowNegative", isSignal: true, isRequired: false, transformFunction: null }, allowScientificNotation: { classPropertyName: "allowScientificNotation", publicName: "allowScientificNotation", isSignal: true, isRequired: false, transformFunction: null }, textAlign: { classPropertyName: "textAlign", publicName: "textAlign", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "input": "onInput($event)", "focus": "onFocus()", "blur": "onBlur()", "paste": "onPaste($event)", "keydown": "onKeyDown($event)", "keypress": "onKeyPress($event)" }, properties: { "style.text-align": "textAlign()" } }, providers: [
            {
                provide: NG_VALUE_ACCESSOR,
                useExisting: forwardRef(() => NumericMaskDirective),
                multi: true,
            },
            {
                provide: NG_VALIDATORS,
                useExisting: forwardRef(() => NumericMaskDirective),
                multi: true,
            },
        ], ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NumericMaskDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[sNumericMask]',
                    standalone: true,
                    host: {
                        '(input)': 'onInput($event)',
                        '(focus)': 'onFocus()',
                        '(blur)': 'onBlur()',
                        '(paste)': 'onPaste($event)',
                        '(keydown)': 'onKeyDown($event)',
                        '(keypress)': 'onKeyPress($event)',
                        '[style.text-align]': 'textAlign()',
                    },
                    providers: [
                        {
                            provide: NG_VALUE_ACCESSOR,
                            useExisting: forwardRef(() => NumericMaskDirective),
                            multi: true,
                        },
                        {
                            provide: NG_VALIDATORS,
                            useExisting: forwardRef(() => NumericMaskDirective),
                            multi: true,
                        },
                    ],
                }]
        }], ctorParameters: () => [] });

class CurrencyMaskModule {
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CurrencyMaskModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
    static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: CurrencyMaskModule, imports: [NumericMaskDirective], exports: [NumericMaskDirective] });
    static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CurrencyMaskModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CurrencyMaskModule, decorators: [{
            type: NgModule,
            args: [{
                    imports: [NumericMaskDirective],
                    exports: [NumericMaskDirective],
                }]
        }] });

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

export { CurrencyMaskModule, NumericMaskDirective };
//# sourceMappingURL=seniorsistemas-angular-components-numeric-mask.mjs.map
