<script setup lang="ts">
import type { TextareaHTMLAttributes } from 'vue'
import { VvTextareaEvents, VvTextareaProps } from '.'
import HintSlotFactory from '../common/HintSlot'
import VvDropdown from '../VvDropdown/VvDropdown.vue'
import VvDropdownOption from '../VvDropdown/VvDropdownOption.vue'
import VvIcon from '../VvIcon/VvIcon.vue'

// props, emit and slots
const props = defineProps(VvTextareaProps)

const emit = defineEmits(VvTextareaEvents)

const slots = useSlots()

// inject plugin
const volver = useVolver()

// props merged with volver defaults (now only for labels)
const propsDefaults = useDefaults<typeof VvTextareaProps>(
    'VvTextarea',
    VvTextareaProps,
    props,
)

// template refs
const textareaEl = ref<HTMLTextAreaElement>()
const wrapperEl = ref<HTMLDivElement>()
const suggestionsDropdownEl = ref<InstanceType<typeof VvDropdown>>()

// data
const {
    id,
    icon,
    iconPosition,
    iconRemoveSuggestion,
    labelRemoveSuggestion,
    label,
    modelValue,
    count,
    valid,
    invalid,
    loading,
    modifiers,
    debounce,
    minlength,
    maxlength,
    storageType,
} = toRefs(props)
const hasId = useUniqueId(id)
const hasHintId = computed(() => `${hasId.value}-hint`)
// BUG - https://www.samanthaming.com/tidbits/88-css-placeholder-shown/
const hasPlaceholder = computed(() =>
    props.floating && isEmpty(props.placeholder) ? ' ' : props.placeholder,
)

// debounce
const localModelValue = useDebouncedInput(modelValue, emit, debounce?.value)

// icons
const { hasIconBefore, hasIconAfter } = useComponentIcon(icon, iconPosition)
const { hasIcon: hasIconRemoveSuggestion }
    = useComponentIcon(iconRemoveSuggestion)

// focus
const { focused } = useComponentFocus(textareaEl, emit)
const isFocused = computed(
    () => focused.value && !props.disabled && !props.readonly,
)
watch(isFocused, (newValue) => {
    if (newValue && propsDefaults.value.selectOnFocus && textareaEl.value) {
        textareaEl.value.select()
    }
    if (newValue && suggestions.value?.size) {
        suggestionsDropdownEl.value?.show()
        return
    }
    if (isDirty.value && suggestions.value) {
        const suggestionsLimit = props.maxSuggestions

        if (
            suggestions.value.size >= suggestionsLimit
            && !suggestions.value.has(localModelValue.value)
        ) {
            suggestions.value = new Set(
                [...suggestions.value].slice(
                    suggestions.value.size - suggestionsLimit + 1,
                ),
            )
        }
        suggestions.value.add(localModelValue.value)
    }
})

// visibility
const isVisible = useElementVisibility(textareaEl)
watch(isVisible, (newValue) => {
    if (newValue && props.autofocus) {
        focused.value = true
    }
})

// count
const { formatted: countFormatted } = useTextCount(localModelValue, {
    mode: count?.value,
    upperLimit: Number(maxlength?.value),
    lowerLimit: Number(minlength?.value),
})

// tabindex
const isClickable = computed(() => !props.disabled && !props.readonly)
const hasTabindex = computed(() =>
    isClickable.value ? props.tabindex : -1,
)

// dirty
const isDirty = computed(() => !isEmpty(modelValue))

// invalid
const isInvalid = computed(() => {
    if (props.invalid === true) {
        return true
    }
    if (props.valid === true) {
        return false
    }
    return undefined
})

// suggestions
const storageKey = computed(() => props.storageKey ?? (volver?.experimentalFeatures.forceInputSuggestions ? props.name : undefined))
const suggestions = usePersistence<Set<string>>(
    storageKey,
    storageType,
    new Set(),
)
const filteredSuggestions = computed(() => {
    if (!suggestions.value) {
        return []
    }
    return [...suggestions.value]
        .filter(
            suggestion =>
                isEmpty(localModelValue.value)
                || (`${suggestion}`
                    .toLowerCase()
                    .includes(`${localModelValue.value}`.toLowerCase())
                    && suggestion !== localModelValue.value),
        )
        .reverse()
})
const hasSuggestions = computed(
    () =>
        storageKey?.value
        && suggestions.value
        && suggestions.value.size > 0,
)
function onSuggestionSelect(suggestion: string) {
    localModelValue.value = suggestion
    suggestionsDropdownEl.value?.hide()
    emit('suggestion:selected', suggestion)
}
function onSuggestionRemove(suggestion: string) {
    suggestions.value?.delete(suggestion)
    emit('suggestion:removed', suggestion)
}

// hint
const {
    HintSlot,
    hasHintLabelOrSlot,
    hasInvalidLabelOrSlot,
    hintSlotScope,
} = HintSlotFactory(propsDefaults, slots)

// styles
const bemCssClasses = useModifiers(
    'vv-textarea',
    modifiers,
    computed(() => ({
        'valid': valid.value,
        'invalid': invalid.value,
        'loading': loading.value,
        'disabled': props.disabled,
        'readonly': props.readonly,
        'required': props.required,
        'icon-before': hasIconBefore.value !== undefined,
        'icon-after': hasIconAfter.value !== undefined,
        'floating': props.floating && !isEmpty(props.label),
        'dirty': isDirty.value,
        'focus': focused.value,
        'resizable': props.resizable,
    })),
)

// attrs
const hasAttrs = computed(
    () =>
        ({
            'name': props.name,
            'placeholder': hasPlaceholder.value,
            'tabindex': hasTabindex.value,
            'disabled': props.disabled,
            'readonly': props.readonly,
            'required': props.required,
            'autocomplete': props.autocomplete,
            'minlength': props.minlength,
            'maxlength': props.maxlength,
            'cols': props.cols,
            'rows': props.rows,
            'wrap': props.wrap,
            'spellcheck': props.spellcheck,
            'aria-invalid': isInvalid.value,
            'aria-describedby': hasHintLabelOrSlot.value
                ? hasHintId.value
                : undefined,
            'aria-errormessage': hasInvalidLabelOrSlot.value
                ? hasHintId.value
                : undefined,
        }) as TextareaHTMLAttributes,
)

// slots props
const slotProps = computed(() => ({
    valid: props.valid,
    invalid: props.invalid,
    modelValue: props.modelValue,
    hintLabel: props.hintLabel,
    maxlength: props.maxlength,
    minlength: props.minlength,
    clear: onClear,
}))

// methods
function onClear() {
    localModelValue.value = undefined
}
</script>

<script lang="ts">
export default {
    name: 'VvTextarea',
}
</script>

<template>
    <div :class="bemCssClasses">
        <label v-if="label" :for="hasId" class="vv-textarea__label">
            {{ label }}
        </label>
        <div ref="wrapperEl" class="vv-textarea__wrapper">
            <!-- @slot Slot to replace icon before textarea -->
            <div v-if="$slots.before" class="vv-textarea__input-before">
                <!-- @slot Slot before input -->
                <slot name="before" v-bind="slotProps" />
            </div>
            <div class="vv-textarea__inner">
                <VvIcon
                    v-if="hasIconBefore"
                    v-bind="hasIconBefore"
                    class="vv-textarea__icon"
                />
                <textarea
                    :id="hasId"
                    ref="textareaEl"
                    v-model="localModelValue"
                    v-bind="hasAttrs"
                    @keyup="emit('keyup', $event)"
                />
                <VvIcon
                    v-if="hasIconAfter"
                    v-bind="hasIconAfter"
                    class="vv-textarea__icon vv-textarea__icon-after"
                />
            </div>
            <div v-if="$slots.after" class="vv-textarea__input-after">
                <!-- @slot Slot after input -->
                <slot name="after" v-bind="slotProps" />
            </div>
            <span v-if="count" class="vv-textarea__limit">
                <!-- @slot Slot to replace count -->
                <slot name="count" v-bind="slotProps">
                    {{ countFormatted }}
                </slot>
            </span>
        </div>
        <HintSlot :id="hasHintId" class="vv-textarea__hint">
            <template v-if="$slots.hint" #hint>
                <slot name="hint" v-bind="hintSlotScope" />
            </template>
            <template v-if="$slots.loading" #loading>
                <slot name="loading" v-bind="hintSlotScope" />
            </template>
            <template v-if="$slots.valid" #valid>
                <slot name="valid" v-bind="hintSlotScope" />
            </template>
            <template v-if="$slots.invalid" #invalid>
                <slot name="invalid" v-bind="hintSlotScope" />
            </template>
        </HintSlot>
        <VvDropdown
            v-if="hasSuggestions"
            ref="suggestionsDropdownEl"
            :reference="wrapperEl"
            :autofocus-first="false"
            :trigger-width="true"
        >
            <template #items>
                <VvDropdownOption
                    v-for="value in filteredSuggestions"
                    :key="value"
                    @click.stop="onSuggestionSelect(value)"
                >
                    <div class="flex-1">
                        <slot name="suggestion" v-bind="{ value }">
                            {{ value }}
                        </slot>
                    </div>
                    <button
                        v-if="suggestions && hasIconRemoveSuggestion"
                        type="button"
                        tabindex="-1"
                        class="cursor-pointer"
                        :title="labelRemoveSuggestion"
                        @click.stop="onSuggestionRemove(value)"
                    >
                        <VvIcon v-bind="hasIconRemoveSuggestion" />
                    </button>
                </VvDropdownOption>
            </template>
        </VvDropdown>
    </div>
</template>
