<template>
    <div
        ref="container"
        :class="cx('root')"
        role="listbox"
        aria-orientation="horizontal"
        :aria-label="ariaLabel"
        :aria-labelledby="ariaLabelledby"
        :aria-activedescendant="focused && focusedItemId ? focusedItemId : undefined"
        @click="onContainerClick"
        :data-p="containerDataP"
        v-bind="ptmi('root')"
    >
        <slot v-for="(tag, index) of d_value" :key="`${index}_${tag}`" name="chip" :class="cx('item', { i: index })" :value="tag" :index="index" :chipProps="getChipProps(tag, index)" :removeCallback="(event) => removeItem(event, index)">
            <Chip :label="tag" removable :unstyled="unstyled" @remove="removeItem($event, index)" :pt="ptm('pcChip')" v-bind="getChipProps(tag, index)">
                <template #removeicon>
                    <slot name="chipicon" :class="cx('chipIcon')" :index="index" :removeCallback="(event) => removeItem(event, index)" />
                </template>
            </Chip>
        </slot>
        <AutoComplete
            ref="autocomplete"
            v-model="inputValue"
            :suggestions="suggestions"
            :typeahead="typeahead"
            :optionLabel="optionLabel"
            :optionDisabled="optionDisabled"
            :optionGroupLabel="optionGroupLabel"
            :optionGroupChildren="optionGroupChildren"
            :scrollHeight="scrollHeight"
            :placeholder="placeholder"
            :dataKey="dataKey"
            :minLength="minLength"
            :delay="delay"
            :appendTo="appendTo"
            :inputId="inputId"
            :inputStyle="inputStyle"
            :inputClass="[inputClass, cx('pcInputText')]"
            :inputProps="inputProps"
            :overlayStyle="overlayStyle"
            :overlayClass="overlayClass"
            :autoOptionFocus="autoOptionFocus"
            :focusOnHover="focusOnHover"
            :searchMessage="searchMessage"
            :emptySearchMessage="emptySearchMessage"
            :emptyMessage="emptyMessage"
            :showEmptyMessage="showEmptyMessage"
            :ariaLabel="ariaLabel"
            :ariaLabelledby="ariaLabelledby"
            :disabled="disabled"
            :invalid="$invalid"
            :unstyled="unstyled"
            :class="cx('pcAutoComplete')"
            :pt="ptm('pcAutoComplete')"
            @focus="onInputFocus"
            @blur="onInputBlur"
            @keydown="onInputKeyDown"
            @paste="onInputPaste"
            @complete="$emit('complete', $event)"
            @option-select="onOptionSelect"
            @before-show="$emit('before-show')"
            @show="$emit('show')"
            @before-hide="$emit('before-hide')"
            @hide="$emit('hide')"
        >
            <template v-if="$slots.option" #option="scope"><slot name="option" v-bind="scope" /></template>
            <template v-if="$slots.optiongroup" #optiongroup="scope"><slot name="optiongroup" v-bind="scope" /></template>
            <template v-if="$slots.header" #header="scope"><slot name="header" v-bind="scope" /></template>
            <template v-if="$slots.footer" #footer="scope"><slot name="footer" v-bind="scope" /></template>
            <template #empty>
                <slot v-if="inputValue && inputValue.length" name="emptysearch">{{ emptySearchMessageText }}</slot>
                <slot v-else name="empty">{{ emptyMessageText }}</slot>
            </template>
        </AutoComplete>
        <input type="hidden" :name="$formName" :value="hiddenInputValue" v-bind="ptm('hiddenInput')" :data-p-hidden-accessible="true" />
    </div>
</template>

<script>
import { cn } from '@primeuix/utils';
import { focus } from '@primeuix/utils/dom';
import { isEmpty, isNotEmpty, resolveFieldData } from '@primeuix/utils/object';
import AutoComplete from 'primevue/autocomplete';
import Chip from 'primevue/chip';
import { mergeProps } from 'vue';
import BaseInputTags from './BaseInputTags.vue';

export default {
    name: 'InputTags',
    extends: BaseInputTags,
    inheritAttrs: false,
    emits: ['focus', 'blur', 'add', 'remove', 'option-select', 'complete', 'before-show', 'before-hide', 'show', 'hide'],
    data() {
        return {
            focused: false,
            focusedItemIndex: -1,
            inputValue: ''
        };
    },
    methods: {
        getInputEl() {
            return this.$refs.autocomplete?.$el?.querySelector('input') ?? null;
        },
        getChipProps(tag, index) {
            return mergeProps(
                {
                    id: `${this.$id}_inputtags_item_${index}`,
                    class: this.cx('item', { i: index }),
                    role: 'option',
                    'aria-label': tag,
                    'aria-selected': this.focusedItemIndex === index,
                    'aria-setsize': this.d_value.length,
                    'aria-posinset': index + 1,
                    'data-p-focused': this.focusedItemIndex === index || undefined,
                    'data-index': index
                },
                this.ptm('item') || {}
            );
        },
        addItem(event, value) {
            const trimmed = (value || '').trim();

            if (!trimmed || this.disabled) return;

            const list = this.d_value ?? [];

            if (this.max && list.length >= this.max) return;
            if (!this.allowDuplicate && list.indexOf(trimmed) !== -1) return;

            this.writeValue([...list, trimmed], event);
            this.$emit('add', { originalEvent: event, value: trimmed });

            const el = this.getInputEl();

            if (el) el.value = '';
            this.inputValue = '';
        },
        removeItem(event, index) {
            if (this.disabled) return;
            event.stopPropagation();

            const removed = this.d_value[index];
            const newValue = this.d_value.filter((_, i) => i !== index);

            this.focusedItemIndex = -1;
            this.writeValue(newValue, event);
            this.$emit('remove', { originalEvent: event, value: removed, index });

            const el = this.getInputEl();

            if (el) focus(el);
        },
        onContainerClick(event) {
            if (this.disabled) return;

            const inputEl = this.getInputEl();

            if (!inputEl) return;

            if (event.target !== inputEl && !inputEl.contains(event.target)) {
                focus(inputEl);
            }
        },
        onInputFocus(event) {
            this.focused = true;
            this.$emit('focus', event);
        },
        onInputBlur(event) {
            if (this.addOnBlur && event?.target?.value?.trim()) {
                this.addItem(event, event.target.value);
            }

            this.focused = false;
            this.focusedItemIndex = -1;
            this.$emit('blur', event);
        },
        onOptionSelect(event) {
            const option = event.value;
            const label = this.optionLabel ? resolveFieldData(option, this.optionLabel) : option;

            this.addItem(event.originalEvent, label);
            this.$emit('option-select', event);
        },
        onInputPaste(event) {
            if (!this.addOnPaste && !this.delimiter) return;

            const pastedData = (event.clipboardData || window['clipboardData']).getData('Text');

            if (!pastedData) return;

            const parts = pastedData.split(this.delimiterRegex || /\r?\n/);

            if (parts.length > 1 || this.addOnPaste) {
                event.preventDefault();

                parts.forEach((part) => this.addItem(event, part));
            }
        },
        onInputKeyDown(event) {
            if (this.disabled) {
                event.preventDefault();

                return;
            }

            switch (event.code) {
                case 'ArrowLeft':
                    this.onArrowLeftKey(event);
                    break;
                case 'ArrowRight':
                    this.onArrowRightKey(event);
                    break;
                case 'Backspace':
                    this.onBackspaceKey(event);
                    break;
                case 'Delete':
                    this.onDeleteKey(event);
                    break;
                case 'Enter':
                case 'NumpadEnter':
                    this.onEnterKey(event);
                    break;
                case 'Tab':
                    this.onTabKey(event);
                    break;
                default:
                    this.onDelimiterKey(event);
                    break;
            }
        },
        onDelimiterKey(event) {
            if (!this.delimiter) return;

            const matches = typeof this.delimiter === 'string' ? event.key === this.delimiter : event.key.match(this.delimiter);

            if (!matches) return;

            event.preventDefault();

            const value = event.target.value;

            if (value && value.trim().length) {
                this.addItem(event, value);
            }
        },
        onArrowLeftKey(event) {
            if (!isEmpty(event.target.value) || !this.$filled) return;

            if (this.focusedItemIndex === -1) {
                this.focusedItemIndex = this.d_value.length - 1;
            } else {
                this.focusedItemIndex = this.focusedItemIndex < 1 ? 0 : this.focusedItemIndex - 1;
            }

            event.preventDefault();
        },
        onArrowRightKey(event) {
            if (this.focusedItemIndex === -1) return;

            this.focusedItemIndex++;

            if (this.focusedItemIndex > this.d_value.length - 1) {
                this.focusedItemIndex = -1;
            }

            event.preventDefault();
        },
        onBackspaceKey(event) {
            if (!isNotEmpty(this.d_value) || event.target.value) return;

            const indexToRemove = this.focusedItemIndex !== -1 ? this.focusedItemIndex : this.d_value.length - 1;

            this.removeItem(event, indexToRemove);
        },
        onDeleteKey(event) {
            if (this.focusedItemIndex === -1 || event.target.value) return;

            this.removeItem(event, this.focusedItemIndex);
        },
        onEnterKey(event) {
            if (event.defaultPrevented || this.isOptionSelectionPending()) return;

            const value = event.target.value;

            if (value && value.trim().length) {
                event.preventDefault();
                this.addItem(event, value);
            }
        },
        isOptionSelectionPending() {
            const autocomplete = this.$refs.autocomplete;

            return this.typeahead && autocomplete?.overlayVisible && autocomplete?.focusedOptionIndex !== -1;
        },
        onTabKey(event) {
            if (!this.addOnTab) return;

            const value = event.target.value;

            if (value && value.trim().length) {
                this.addItem(event, value);
            }
        }
    },
    computed: {
        delimiterRegex() {
            if (!this.delimiter) return null;

            return this.delimiter instanceof RegExp ? this.delimiter : new RegExp(this.delimiter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
        },
        hiddenInputValue() {
            return (this.d_value || []).join(',');
        },
        focusedItemId() {
            return this.focusedItemIndex !== -1 ? `${this.$id}_inputtags_item_${this.focusedItemIndex}` : null;
        },
        emptyMessageText() {
            return this.emptyMessage || this.$primevue?.config?.locale?.emptyMessage || '';
        },
        emptySearchMessageText() {
            return this.emptySearchMessage || this.$primevue?.config?.locale?.emptySearchMessage || '';
        },
        containerDataP() {
            return cn({
                invalid: this.$invalid,
                disabled: this.disabled,
                focus: this.focused,
                fluid: this.$fluid,
                filled: this.$variant === 'filled',
                empty: !this.$filled,
                [this.size]: this.size
            });
        }
    },
    components: {
        Chip,
        AutoComplete
    }
};
</script>
