import * as i0 from '@angular/core';
import { input, model, output, signal, computed, viewChildren, inject, ElementRef, DestroyRef, effect, forwardRef, Component, NgModule } from '@angular/core';
import { NgClass } from '@angular/common';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import * as i1 from '@ngx-translate/core';
import { TranslateModule } from '@ngx-translate/core';

/**
 * @description Componente de entrada de código PIN/OTP com campos individuais para cada dígito.
 * Implementa {@link ControlValueAccessor} para integração nativa com Reactive Forms
 * e Template-driven Forms do Angular.
 *
 * Funcionalidades:
 * - Campos separados e navegáveis para cada dígito do código
 * - Comprimento configurável via input `length`
 * - Suporte a entrada numérica ou alfanumérica
 * - Navegação por teclado (setas, backspace)
 * - Suporte a colar (paste) com truncamento automático ao tamanho definido
 * - Estado de erro via input `invalid`
 * - Acessibilidade completa com ARIA labels e roles semânticos
 *
 * @example
 * ```html
 * <form [formGroup]="form">
 *   <s-pin-code-field
 *     formControlName="code"
 *     [length]="6"
 *     [alphanumeric]="false"
 *     [invalid]="(form.get('code')?.invalid && form.get('code')?.dirty) || false"
 *     helpText="Digite seu código de 6 dígitos"
 *     (codeFilled)="onCodeFilled($event)">
 *   </s-pin-code-field>
 * </form>
 * ```
 *
 * @category Inputs
 */
class PinCodeFieldComponent {
    static LAST_INDEX_OFFSET = 1;
    static EMPTY_VALUE = '';
    /**
     * @description Número de campos de entrada (comprimento do código PIN).
     * Deve ser ao menos 1.
     *
     * @default 6
     */
    length = input(6);
    /**
     * @description Permite entrada alfanumérica e de caracteres especiais.
     * Quando `false`, aceita apenas dígitos numéricos (0–9).
     *
     * @default false
     */
    alphanumeric = input(false);
    /**
     * @description Texto de ajuda exibido abaixo dos campos de entrada,
     * fornecendo instruções adicionais ao usuário.
     *
     * @default ''
     */
    helpText = input('');
    /**
     * @description Controla a visualização do estado de erro do componente.
     * Quando `true`, exibe borda vermelha nos campos de entrada.
     * Normalmente vinculado ao estado `invalid && dirty` do `FormControl`.
     *
     * @example
     * ```html
     * [invalid]="(control?.invalid && control?.dirty) || false"
     * ```
     * @default false
     */
    invalid = input(false);
    /**
     * @description Controla o estado desabilitado do componente via two-way binding.
     * Quando `true`, todos os campos ficam desabilitados e a interação do usuário é bloqueada.
     *
     * @default false
     */
    disabled = model(false);
    /**
     * @description Emitido quando todos os campos do código PIN são preenchidos.
     * Emite o código completo como string.
     *
     * @example
     * ```html
     * (codeFilled)="onCodeFilled($event)"
     * ```
     */
    codeFilled = output();
    /**
     * @description Signal com os valores atuais de cada campo de entrada.
     * O comprimento do array corresponde ao valor do input `length`.
     */
    inputs = signal([]);
    /**
     * Signal tracking whether the component has been touched by the user.
     * Set to true when the last input field loses focus (blur event).
     */
    touched = signal(false);
    /**
     * Computed signal indicating whether all PIN code fields are filled.
     * True only when every field contains a non-empty value.
     */
    isComplete = computed(() => this.inputs().every((input) => input !== ''));
    /**
     * Computed signal containing the complete PIN code as a joined string.
     * Returns empty string if no inputs are filled.
     */
    currentValue = computed(() => this.inputs().join(''));
    /**
     * Signal tracking if the component host has the ng-invalid class from FormControl validators.
     * Updated reactively whenever the invalid state changes.
     */
    hasNgInvalidClass = signal(false);
    /**
     * Signal tracking if the FormControl is dirty.
     * Updated reactively whenever the dirty state changes.
     */
    isControlDirty = signal(false);
    /**
     * Computed signal indicating if the component should show error state.
     * Returns true if:
     * - The `invalid` input is true, OR
     * - The component has the `ng-invalid` class AND the FormControl is dirty (touched/modified)
     * This ensures errors are only shown to the user after they've interacted with the field.
     */
    hasError = computed(() => this.invalid() || (this.hasNgInvalidClass() && this.isControlDirty()));
    /**
     * ViewChildren reference to all input DOM elements.
     * Used for DOM manipulation (focus, selection).
     */
    pinInputs = viewChildren('pinInput');
    elementRef = inject(ElementRef);
    destroyRef = inject(DestroyRef);
    onChange = () => { };
    onTouched = () => { };
    constructor() {
        effect(() => {
            const newLength = this.length();
            if (newLength < 1) {
                console.warn('PIN code length must be at least 1');
                return;
            }
            if (this.inputs().length !== newLength) {
                this.inputs.set(Array.from({ length: newLength }, () => ''));
            }
        }, { allowSignalWrites: true });
        // Monitor ng-invalid and ng-dirty classes on the host element for reactive error state
        effect(() => {
            // Access inputs to create dependency on changes
            this.inputs();
            this.invalid();
            // Use setTimeout to defer the check to next tick to ensure Angular has updated DOM classes
            const timeoutId = window.setTimeout(() => {
                const hostElement = this.elementRef.nativeElement;
                const hasNgInvalid = hostElement.classList.contains('ng-invalid');
                const isDirty = hostElement.classList.contains('ng-dirty');
                this.hasNgInvalidClass.set(hasNgInvalid);
                this.isControlDirty.set(isDirty);
            });
            // Cleanup timeout on component destroy
            this.destroyRef.onDestroy(() => {
                clearTimeout(timeoutId);
            });
        }, { allowSignalWrites: true });
    }
    /**
     * Handles input event on PIN code fields.
     * Validates character input, prevents multiple characters per field,
     * auto-advances to next field when character is entered.
     *
     * @param index - The input field index (0-based)
     * @param event - The input event
     */
    onInput(index, event) {
        if (this.disabled()) {
            event.preventDefault();
            return;
        }
        const target = event.target;
        const value = target.value;
        if (value.length > 1 || (value && !this.isValidCharacter(value))) {
            this.clearInput(target);
            return;
        }
        this.setInputAtIndex(index, value);
        if (value && index < this.length() - 1) {
            this.focusAndSelectInput(index + 1);
        }
        if (this.isComplete()) {
            this.codeFilled.emit(this.currentValue());
        }
    }
    /**
     * Handles keyboard events on PIN code fields.
     * Supports arrow navigation (left/right) and backspace key.
     *
     * Keyboard behavior:
     * - ArrowRight: Move focus to next field
     * - ArrowLeft: Move focus to previous field
     * - Backspace: Clear current or previous field and adjust focus
     *
     * @param index - The input field index (0-based)
     * @param event - The keyboard event
     */
    onKeyDown(index, event) {
        if (this.disabled()) {
            event.preventDefault();
            return;
        }
        const key = event.key;
        switch (key) {
            case 'ArrowRight':
                if (index < this.length() - 1) {
                    event.preventDefault();
                    this.focusAndSelectInput(index + 1);
                }
                break;
            case 'ArrowLeft':
                if (index > 0) {
                    event.preventDefault();
                    this.focusAndSelectInput(index - 1);
                }
                break;
            case 'Backspace':
                event.preventDefault();
                this.handleBackspace(index);
                break;
        }
    }
    /**
     * Handles paste (clipboard) events on PIN code fields.
     * Extracts valid characters from pasted content, filters by character type,
     * and automatically truncates to match field length.
     *
     * Behavior:
     * - Filters characters based on `alphanumeric` setting
     * - Truncates to maximum field count
     * - Fills available fields with pasted content
     * - Emits codeFilled event if all fields are filled
     *
     * @param event - The clipboard event
     */
    onPaste(event) {
        if (this.disabled()) {
            event.preventDefault();
            return;
        }
        event.preventDefault();
        const validChars = this.extractValidCharactersFromPaste(event);
        if (validChars.length === 0) {
            return;
        }
        const filledInputs = [...validChars, ...Array(this.length() - validChars.length).fill('')];
        this.inputs.set(filledInputs);
        this.updateValue();
        // Focus on the first empty input or the last filled input if all are filled
        const firstEmptyIndex = validChars.length < this.length() ? validChars.length : validChars.length - 1;
        this.focusAndSelectInput(firstEmptyIndex);
        if (this.isComplete()) {
            this.codeFilled.emit(this.currentValue());
        }
    }
    /**
     * Handles focus event on PIN code fields.
     * Selects all text in the input field when focused.
     *
     * @param index - The input field index (0-based)
     */
    onFocus(index) {
        const input = this.pinInputs()[index];
        if (input) {
            input.nativeElement.select();
        }
    }
    /**
     * Handles blur event on PIN code fields.
     * Marks the component as touched when the last input field loses focus.
     * Also notifies the registered onTouched callback for FormControl integration.
     *
     * @param index - The input field index (0-based)
     */
    onBlur(index) {
        // Only mark as touched when the LAST input loses focus
        if (index === this.length() - PinCodeFieldComponent.LAST_INDEX_OFFSET) {
            this.touched.set(true);
            this.onTouched();
        }
    }
    /**
     * Clears the input value at specified index and adjusts focus accordingly.
     * If current field is empty, moves to previous field and clears it.
     *
     * @private
     * @param index - The input field index (0-based)
     */
    handleBackspace(index) {
        const currentInputs = this.inputs();
        // If current input has value, clear it
        if (currentInputs[index]) {
            this.setInputAtIndex(index, PinCodeFieldComponent.EMPTY_VALUE);
            return;
        }
        // Otherwise, clear previous input and focus it
        if (index > 0) {
            this.setInputAtIndex(index - 1, PinCodeFieldComponent.EMPTY_VALUE);
            this.focusAndSelectInput(index - 1);
        }
    }
    /**
     * Sets the value at a specific input field index and updates the form value.
     * Updates the signal and triggers the change detection via onChange callback.
     *
     * @private
     * @param index - The input field index (0-based)
     * @param value - The value to set in the field
     */
    setInputAtIndex(index, value) {
        const currentInputs = this.inputs();
        currentInputs[index] = value;
        this.inputs.set([...currentInputs]);
        this.updateValue();
    }
    /**
     * Focuses on and selects text in the input field at the specified index.
     * Uses queueMicrotask to ensure DOM is updated before focusing.
     *
     * @private
     * @param index - The input field index (0-based)
     */
    focusAndSelectInput(index) {
        queueMicrotask(() => {
            const input = this.pinInputs()[index];
            if (input) {
                input.nativeElement.focus();
                input.nativeElement.select();
            }
        });
    }
    /**
     * Clears the value of an input element.
     * Used for input validation to reject invalid characters.
     *
     * @private
     * @param target - The HTML input element to clear
     */
    clearInput(target) {
        target.value = '';
    }
    /**
     * Extracts valid characters from pasted content.
     * Filters based on alphanumeric setting and limits to component's length.
     *
     * @private
     * @param event - The clipboard event
     * @returns Array of valid characters from the pasted content
     */
    extractValidCharactersFromPaste(event) {
        const pastedText = (event.clipboardData?.getData('text') || '').trim();
        return pastedText
            .split('')
            .filter((char) => this.isValidCharacter(char))
            .slice(0, this.length());
    }
    /**
     * Validates if a character is acceptable based on component configuration.
     * For numeric mode, accepts only digits 0-9.
     * For alphanumeric mode, accepts any non-whitespace character.
     *
     * @private
     * @param char - The character to validate
     * @returns true if character is valid, false otherwise
     */
    isValidCharacter(char) {
        if (this.alphanumeric()) {
            return char.trim().length > 0;
        }
        return /^[0-9]$/.test(char);
    }
    /**
     * Updates the form value by calling the onChange callback registered by FormControl.
     * This ensures the reactive form stays synchronized with component state.
     *
     * @private
     */
    updateValue() {
        this.onChange(this.currentValue());
    }
    /**
     * Implements ControlValueAccessor interface.
     * Called by the FormControl to write a value to the component.
     * Only accepts string values with length matching the component's length setting.
     *
     * @param value - The value to write (typically from the FormControl)
     */
    writeValue(value) {
        if (value) {
            const stringValue = String(value);
            if (stringValue.length === this.length()) {
                this.inputs.set(stringValue.split(''));
            }
        }
    }
    /**
     * Implements ControlValueAccessor interface.
     * Called by the FormControl when value changes.
     * Registers the callback to be called when the component value changes.
     *
     * @param fn - Callback function to invoke when component value changes
     */
    registerOnChange(fn) {
        this.onChange = fn;
    }
    /**
     * Implements ControlValueAccessor interface.
     * Called by the FormControl to register the touched callback.
     * The callback is invoked when the last input field loses focus.
     *
     * @param fn - Callback function to invoke when component is touched
     */
    registerOnTouched(fn) {
        this.onTouched = fn;
    }
    /**
     * Implements ControlValueAccessor interface.
     * Called by the FormControl to set the disabled state of the component.
     * Updates the disabled model to reflect the form's disabled state.
     *
     * @param isDisabled - Whether the component should be disabled
     */
    setDisabledState(isDisabled) {
        this.disabled.set(isDisabled);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PinCodeFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: PinCodeFieldComponent, isStandalone: true, selector: "s-pin-code-field", inputs: { length: { classPropertyName: "length", publicName: "length", isSignal: true, isRequired: false, transformFunction: null }, alphanumeric: { classPropertyName: "alphanumeric", publicName: "alphanumeric", isSignal: true, isRequired: false, transformFunction: null }, helpText: { classPropertyName: "helpText", publicName: "helpText", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", codeFilled: "codeFilled" }, providers: [
            {
                provide: NG_VALUE_ACCESSOR,
                useExisting: forwardRef(() => PinCodeFieldComponent),
                multi: true,
            },
        ], viewQueries: [{ propertyName: "pinInputs", predicate: ["pinInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"gap-xsmall flex flex-col\">\n    <div\n        class=\"flex gap-2\"\n        role=\"group\"\n        aria-label=\"PIN code input\"\n        [attr.aria-labelledby]=\"helpText() ? 'pin-help-text' : null\"\n    >\n        @for (input of inputs(); let i = $index; track i) {\n            <input\n                #pinInput\n                type=\"text\"\n                class=\"h-12 w-12 rounded-xbig border text-center text-lg font-medium transition-all duration-200\"\n                [ngClass]=\"{\n                    'border-criticality-red focus:ring-criticality-red': hasError(),\n                    'border-grayscale-30 focus:border-primary focus:ring-primary': !hasError(),\n                    'cursor-not-allowed bg-grayscale-10 text-grayscale-60': disabled(),\n                    'bg-grayscale-0 text-grayscale-100 focus:outline-none': !disabled(),\n                }\"\n                [value]=\"input\"\n                [disabled]=\"disabled()\"\n                [attr.maxlength]=\"1\"\n                [attr.inputmode]=\"alphanumeric() ? 'text' : 'numeric'\"\n                [attr.aria-label]=\"\n                    alphanumeric()\n                        ? ('platform.angular_components.pin_code_character_index_of_total'\n                          | translate: { index: i + 1, length: length() })\n                        : ('platform.angular_components.pin_code_digit_index_of_total'\n                          | translate: { index: i + 1, length: length() })\n                \"\n                [attr.aria-describedby]=\"helpText() ? 'pin-help-text' : null\"\n                [attr.aria-required]=\"true\"\n                autocomplete=\"off\"\n                (input)=\"onInput(i, $event)\"\n                (keydown)=\"onKeyDown(i, $event)\"\n                (paste)=\"onPaste($event)\"\n                (focus)=\"onFocus(i)\"\n                (blur)=\"onBlur(i)\"\n            />\n        }\n    </div>\n\n    @if (helpText()) {\n        <div\n            id=\"pin-help-text\"\n            class=\"mt-small text-sm text-grayscale-60\"\n        >\n            {{ helpText() }}\n        </div>\n    }\n</div>\n\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PinCodeFieldComponent, decorators: [{
            type: Component,
            args: [{ selector: 's-pin-code-field', standalone: true, imports: [NgClass, TranslateModule], providers: [
                        {
                            provide: NG_VALUE_ACCESSOR,
                            useExisting: forwardRef(() => PinCodeFieldComponent),
                            multi: true,
                        },
                    ], template: "<div class=\"gap-xsmall flex flex-col\">\n    <div\n        class=\"flex gap-2\"\n        role=\"group\"\n        aria-label=\"PIN code input\"\n        [attr.aria-labelledby]=\"helpText() ? 'pin-help-text' : null\"\n    >\n        @for (input of inputs(); let i = $index; track i) {\n            <input\n                #pinInput\n                type=\"text\"\n                class=\"h-12 w-12 rounded-xbig border text-center text-lg font-medium transition-all duration-200\"\n                [ngClass]=\"{\n                    'border-criticality-red focus:ring-criticality-red': hasError(),\n                    'border-grayscale-30 focus:border-primary focus:ring-primary': !hasError(),\n                    'cursor-not-allowed bg-grayscale-10 text-grayscale-60': disabled(),\n                    'bg-grayscale-0 text-grayscale-100 focus:outline-none': !disabled(),\n                }\"\n                [value]=\"input\"\n                [disabled]=\"disabled()\"\n                [attr.maxlength]=\"1\"\n                [attr.inputmode]=\"alphanumeric() ? 'text' : 'numeric'\"\n                [attr.aria-label]=\"\n                    alphanumeric()\n                        ? ('platform.angular_components.pin_code_character_index_of_total'\n                          | translate: { index: i + 1, length: length() })\n                        : ('platform.angular_components.pin_code_digit_index_of_total'\n                          | translate: { index: i + 1, length: length() })\n                \"\n                [attr.aria-describedby]=\"helpText() ? 'pin-help-text' : null\"\n                [attr.aria-required]=\"true\"\n                autocomplete=\"off\"\n                (input)=\"onInput(i, $event)\"\n                (keydown)=\"onKeyDown(i, $event)\"\n                (paste)=\"onPaste($event)\"\n                (focus)=\"onFocus(i)\"\n                (blur)=\"onBlur(i)\"\n            />\n        }\n    </div>\n\n    @if (helpText()) {\n        <div\n            id=\"pin-help-text\"\n            class=\"mt-small text-sm text-grayscale-60\"\n        >\n            {{ helpText() }}\n        </div>\n    }\n</div>\n\n" }]
        }], ctorParameters: () => [] });

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

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

export { PinCodeFieldComponent, PinCodeFieldModule };
//# sourceMappingURL=seniorsistemas-angular-components-pin-code-field.mjs.map
