<script generic="T, K extends keyof T" lang="ts" setup>
import {twMerge, twJoin} from 'tailwind-merge'
import AAlert from '#adata-ui/components/elements/alert/AAlert.vue'

interface Props {
  disabled?: boolean
  label?: string
  size?: 'sm' | 'md'
  error?: string | boolean
  required?: boolean
  readonly?: boolean
  clearable?: boolean
  startIcon?: string | object
  endIcon?: string | object
  autocompleteList?: T[]
  type?: 'number' | 'text' | 'email' | 'tel' | 'search'
  autocompleteOption?: K
  autocompleteKey?: K
  autocompleteFn?: (item: T) => string
  colorClasses?:string
}

const props = withDefaults(defineProps<Props>(), {
  disabled: false,
  label: '',
  size: 'md',
  error: false,
  required: false,
  readonly: false,
  clearable: false,
  startIcon: '',
  endIcon: '',
  type: 'text',
  autocompleteList: () => [],
  autocompleteOption: undefined,
  autocompleteKey: undefined,
  autocompleteFn: undefined
})

const {t} = useI18n()

const uiConfig = {
  wrapper: 'transition duration-500 w-full',
  'input-wrapper': {
    base: 'relative inline-block w-full text-sm sm:min-w-full',
    readonly: 'pointer-events-none user-select-none',
    disabled: 'pointer-events-none user-select-none opacity-40'
  },
  base: 'w-full border-0 rounded-md text-deepblue-900 dark:text-gray-200 hover:outline outline-offset-[-1px] hover:outline-1 hover:outline-blue-700 hover:dark:outline-blue-500 focus:outline focus:outline-blue-700 focus:dark:outline-blue-500 mt-0',
  size: {
    sm: 'h-8 px-4 py-1.5 sm',
    md: 'h-10 px-4 pb-1.5 pt-[18px] md',
    smEx: 'h-8 pl-4 pr-10 py-1.5 sm',
    mdEx: 'h-10 pl-4 pr-10 pb-1.5 pt-[18px] md'
  },
  color: 'bg-deepblue-50 dark:bg-gray-200 dark:bg-opacity-5',
  error:
    'outline outline-1 outline-red-500 focus:outline-0 focus:border-red-500 hover:border-red-500',
  readonly: 'bg-white opacity-30',
  label: {
    base: 'absolute left-4 top-[50%] d-flex text-gray-500 pointer-events-none transition-all ease duration-300 transform translate-y-[-50%] whitespace-nowrap text-ellipsis md:max-w-[90%] overflow-hidden',
    'move-label': '!left-10 md:max-w-[calc(90%-40px)]'
  },
  default: {
    size: 'md',
    color: 'gray'
  },
  'move-input': '!ps-10',
  'move-input-left': '!pe-10'
}

type AutocompleteValue = T | (typeof T)[keyof typeof T]

const emit = defineEmits<{
  (e: 'updateValue', value: string | number): void
  (e: 'onEnter', value : AutocompleteValue): void
  (e: 'clear'): void
  (e: 'selectOption', value: AutocompleteValue): void
}>()

function customTrim(input: string): string {
  if (input === undefined || input === null) {
    return ''
  }
  // Проверка, является ли строка пустой или состоит только из чисел
  if (input === ' ' || /\d/.test(input)) {
    return input.trim()
  }

  // Возвращаем оригинальное значение, если условия не выполнены
  return input
}

const input = ref<HTMLInputElement>()
const [modelValue, modelModifiers] = defineModel<string | number | AutocompleteValue>({default: '', set(value) {
  if (modelModifiers['custom-trim']) {
      return  customTrim(value)
  }
  return value
  }})
const isFocused = ref()

const isClearable = computed<boolean>(
  () => props.clearable && !props.disabled && !!modelValue.value
)

const mobileAutocomMoved = ref(false)

const wrapperClass = uiConfig.wrapper

const inputWrapperClass = computed(() =>
  twMerge(
    twJoin(
      uiConfig['input-wrapper'].base,
      props.readonly ? uiConfig['input-wrapper'].readonly : '',
      props.disabled ? uiConfig['input-wrapper'].disabled : ''
    )
  )
)

const inputClass = computed(() =>
  twMerge(
    twJoin(
      uiConfig.base,
      uiConfig.size[
        (props.clearable ? `${props.size}Ex` : props.size) as 'sm' | 'md' | 'smEx' | 'mdEx'
        ],
      props.error ? uiConfig.error : '',
      props.startIcon ? uiConfig['move-input'] : '',
      props.endIcon ? uiConfig['move-input-left'] : '',
      props.readonly ? uiConfig.readonly : '',
      props.colorClasses ? props.colorClasses : uiConfig.color
    )
  )
)

const labelClass = computed(() =>
  twMerge(twJoin(uiConfig.label.base, isClearable.value ? 'max-w-[calc(80%-40px)]' : 'max-w-[80%]', props.startIcon ? uiConfig.label['move-label'] : ''))
)

const currentItemIndex = ref<number>(-1)

const assignValue = () => {
  if (props.autocompleteFn) {
    modelValue.value = props.autocompleteFn(props.autocompleteList[currentItemIndex.value])
  } else {
    modelValue.value = props.autocompleteKey
      ? props.autocompleteList[currentItemIndex.value][props.autocompleteKey]
      : props.autocompleteList[currentItemIndex.value]
  }
}


const handleKeydown = (e: KeyboardEvent) => {
  if (!props.autocompleteList.length) return
  if (e.key === 'ArrowDown' && currentItemIndex.value < props.autocompleteList.length - 1) {
    e.preventDefault()
    currentItemIndex.value++
    assignValue()
  } else if (e.key === 'ArrowUp' && currentItemIndex.value > 0) {
    e.preventDefault()
    currentItemIndex.value--
    assignValue()
  } else if (e.key === 'Enter') {
    if (currentItemIndex.value !== -1) assignValue()
    emit('onEnter', props.autocompleteList[currentItemIndex.value])
    input.value?.blur()
  }
}

const onSelectOption = (item: T) => {
  if (props.autocompleteKey) {
    modelValue.value = item[props.autocompleteKey]
    emit('selectOption', item[props.autocompleteKey])
  } else {
    modelValue.value = item
    emit('selectOption', item)
  }
}
const onSelectOptionMobile = (item: T) => {
  if (!mobileAutocomMoved.value) {
    onSelectOption(item)
  } else {
    mobileAutocomMoved.value = false
  }
}

const onClear = () => {
  modelValue.value = ''
  emit('clear')
}
const autocompleteContainer = ref()

const scrollToElement = (container: HTMLElement) => {
  if (container) {
    const selectedItem = container.children[currentItemIndex.value] as HTMLElement;
    const containerHeight = container.clientHeight;
    const itemTop = selectedItem.offsetTop;
    const itemHeight = selectedItem.clientHeight;

    if (itemTop < container.scrollTop) {
      container.scrollTop = itemTop;
    } else if (itemTop + itemHeight > container.scrollTop + containerHeight) {
      container.scrollTop = itemTop + itemHeight - containerHeight;
    }
  }
}
const focus = () => {
    input.value?.focus();
};

watch(currentItemIndex, () => {
  scrollToElement(autocompleteContainer.value)
})

const isDropdownUp = ref(false)
const checkDropdownPosition = () => {
  const rect = input.value?.getBoundingClientRect();
  const dropdownHeight = 250;

  const windowHeight = window.visualViewport ? window.visualViewport.height : window.innerHeight;

  if (rect) {
    isDropdownUp.value = (windowHeight - rect.bottom) < dropdownHeight;
  }
};
watch(isFocused, (newVal) => {
  if (newVal) {
    checkDropdownPosition();
  }
});

watch(modelValue, () => {
  checkDropdownPosition();
});



defineExpose({
  focus
})
</script>
<template>
  <div :class="wrapperClass">
    <div :class="inputWrapperClass">
      <Transition name="autocomplete-fade">
        <div
          v-if="autocompleteList.length && isFocused"
          :class="[
            'absolute z-[31] w-full flex flex-col gap-4 rounded bg-gray-200 p-2 dark:bg-gray-800',
            isDropdownUp ? 'bottom-[44px]' : 'top-[44px]'
          ]"
        >
          <ul
            ref="autocompleteContainer"
            class="max-h-[250px] w-full overflow-y-auto"
          >
            <li
              v-for="(item, idx) in autocompleteList"
              :key="idx"
              class="cursor-pointer rounded px-4 py-[10px] transition-colors hover:bg-gray-50 hover:dark:bg-gray-900"
              :class="{ 'bg-gray-100 dark:bg-gray-700': currentItemIndex === idx }"
              @mousedown="onSelectOption(item)"
              @touchend="onSelectOptionMobile(item)"
              @touchmove="mobileAutocomMoved = true"
            >
              <slot
                name="autocomplete-option"
                :item="item"
              >
                {{ autocompleteOption ? item[autocompleteOption as K] : item }}
              </slot>
            </li>
          </ul>
          <slot name='autocomplete-buttons' :value='modelValue' />
        </div>
      </Transition>
      <input
        :id="label"
        ref="input"
        v-model="modelValue"
        :type="type"
        :class="inputClass"
        class="input"
        required
        :tabindex="disabled ? -1 : 0"
        v-bind="$attrs"
        @input="emit('updateValue', modelValue)"
        @keydown="handleKeydown"
        @focus="isFocused = true"
        @blur="isFocused = false"
      >
      <label
        :for="label"
        :class="[{ '!translate-y-[-17px] !text-[10px]': modelValue }, labelClass]"
        class="label"
      >
        <span class="text">{{ label }}</span>
        <span
          v-if="required"
          class="text-red-600"
        > *</span>
      </label>

      <component
        :is="startIcon"
        v-if="startIcon"
        class="pointer-events-none absolute left-4 top-1/2 translate-y-[-50%] transform fill-gray-500 text-base text-gray-500"
        filled
      />
      <component
        :is="endIcon"
        v-if="endIcon"
        class="pointer-events-none absolute right-4 top-1/2 translate-y-[-50%] transform fill-gray-500 text-base text-gray-500"
        filled
      />
      <button
        v-if="isClearable"
        class="pointer-events-click absolute right-4 top-1/2 translate-y-[-50%] transform rounded-full bg-deepblue-900 bg-opacity-10 p-1 text-2xl hover:opacity-80 dark:bg-gray-200 dark:bg-opacity-10"
        type="button"
        @click="onClear"
      >
        <a-icon-x-mark
          filled
          class="!mb-0 fill-gray-600 dark:fill-gray-300"
        />
      </button>
      <slot name="endButton" />
    </div>
    <Transition name="slide-in">
      <div
        v-if="error && typeof error === 'string'"
        class="mt-1"
      >
        <a-alert
          :size="size === 'sm' ? 'xs' : 'sm'"
          :color="'red'"
          icon-type="triangle"
        >
          <template #default>
            <div class="flex flex-col">
              <span
                v-for="(err, index) in error.split(', ')"
                :key="index"
              >{{ err }}<br></span>
            </div>
          </template>
        </a-alert>
      </div>
    </Transition>
  </div>
</template>
<style scoped>
.input {
  &:focus ~ .label,
  &:not(:focus):valid ~ .label,
  &:-webkit-autofill ~ .label {
    font-size: 10px;
    transform: translateY(-17px);
  }

  &.sm {
    &:focus ~ .label,
    &:not(:focus):valid ~ .label,
    &:-webkit-autofill ~ .label {
      display: none;
    }
  }

  &::-ms-reveal,
  &::-ms-clear {
    display: none;
  }
}

.slide-in-enter-active {
  z-index: 0;
  opacity: 0;
  transform: translateY(-50%);
  transition: transform 0.5s;
}

.slide-in-enter-to {
  z-index: auto;
  opacity: 1;
  transform: translateY(0);
}

.slide-in-leave-active {
  opacity: 1;
  transform: translateY(0);
}

.slide-in-leave-to {
  opacity: 0;
  transform: translateY(-100%);
}

.autocomplete-fade-enter-active,
.autocomplete-fade-leave-active {
  transition: opacity 150ms ease;
}

.autocomplete-fade-enter-from,
.autocomplete-fade-leave-to {
  opacity: 0;
}

.move-input {
  padding-left: 40px !important;
}
</style>
