<script generic="T" lang="ts" setup>
import AInput from '~/components/forms/input/standard/AInputStandard.vue'
import AChips from '~/components/elements/chips/AChips.vue'

import { onClickOutside, useToggle } from '@vueuse/core'
import { isEqual } from 'lodash-es'
import type { Ref } from 'vue'

type Props = {
  label: string
  required?: boolean
  options: T[]
  keyToShow?: string
  getOnlyKey?: boolean
  keyToSelect?: string
  clearable?: boolean
  hasButtons?: boolean
  disabled?: boolean
  size?: 'sm' | 'md'
  error?: string | string[]
  startIcon?: object
  searchable?: boolean
  searchLabel?: string
  searchKey?: string
  searchFromOut?: boolean
  multiple?: boolean
  minimalWords?: boolean
  canChipRemove?: boolean
  showFirstItem?: boolean
  buttonBg?: string
  changeableModelValue?: boolean
}

type Emits = {
  (e: 'onChange', option?: T | T[]): void
  (e: 'onSubmit', value?: T | T[]): void
  (e: 'onClear'): void
}

const props = withDefaults(defineProps<Props>(), {
  required: false,
  getOnlyKey: false,
  keyToShow: 'name',
  keyToSelect: 'id',
  clearable: true,
  hasButtons: false,
  disabled: false,
  size: 'sm',
  error: undefined,
  startIcon: undefined,
  searchable: false,
  searchLabel: 'Поиск...',
  searchKey: 'name',
  searchFromOut: false,
  multiple: false,
  minimalWords: false,
  canChipRemove: true,
  showFirstItem: false,
  buttonBg: 'border-deepblue-50 bg-deepblue-50 dark:border-gray-900 dark:bg-gray-200/5',
})

const emits = defineEmits<Emits>()

const modelValue = defineModel<T | T[] | null | any>({ required: true })
const search = defineModel<T | null | any>('search')
const isOpen = defineModel<T | null | any>('open', {
  default: false
})

const searchValue = ref('')
const toggleOpen = useToggle(isOpen)
const wrapper = ref<HTMLDivElement | null>()
const valueToShow = ref<T | T[] | null>()
const countedVisibleItems = ref(0)

const disabledStyles =
  'disabled:pointer-events-none disabled:opacity-40 disabled:bg-deepblue-50 disabled:text-deepblue-200 disabled:dark:text-gray-500'

const componentOptions = computed(() =>
  props.options.filter((item: T) =>
    item[props.searchKey as keyof T].toLowerCase().includes(searchValue.value.toLowerCase())
  )
)

onClickOutside(wrapper, () => {
  if (isOpen.value) toggleOpen(false)
})

function onSelect(item: T) {
  if (props.multiple) {
    if (!Array.isArray(valueToShow.value) && !valueToShow.value) {
      valueToShow.value = []
    }
    if (valueToShow.value.some((element: unknown) => isEqual(item, element))) {
      valueToShow.value = (valueToShow as Ref<T[]>).value.filter(
        (element: unknown) => !isEqual(item, element)
      )
      modelValue.value = props.getOnlyKey
        ? (modelValue as Ref<T[]>).value.filter(
            (element: unknown) => !isEqual(item[props.keyToSelect as keyof T], element)
          )
        : (modelValue as Ref<T[]>).value.filter((element: unknown) => !isEqual(item, element))
      emits('onChange', modelValue.value)
      return
    }
    valueToShow.value.push(item)
    if (Array.isArray(modelValue.value) && props.getOnlyKey) {
      modelValue.value.push(item[props.keyToSelect as keyof T])
      emits('onChange', modelValue.value)
    } else if (!Array.isArray(modelValue.value)) {
      console.error('model value must be array')
    } else {
      modelValue.value.push(item)
      emits('onChange', modelValue.value)
    }
  } else {
    valueToShow.value = item
    if (props.getOnlyKey) {
      modelValue.value = item[props.keyToSelect as keyof T]
      emits('onChange', item)
      toggleOpen(false)
    } else {
      modelValue.value = item
      emits('onChange', item)
      toggleOpen(false)
    }
  }
}

function onClear() {
  if (props.multiple) {
    valueToShow.value = null
    modelValue.value = []
  } else {
    valueToShow.value = null
    modelValue.value = null
  }
  emits('onClear')
}

function onClearFromMulti(index: number) {
  valueToShow.value.splice(index, 1)
  modelValue.value.splice(index, 1)
  if (!valueToShow.value.length) {
    valueToShow.value = null
  }
}

watch(
  () => isOpen.value,
  (newValue: boolean) => {
    if (!newValue) searchValue.value = ''
  }
)
watch(
  () => modelValue.value,
  () => {
    if (modelValue.value?.length == 0) valueToShow.value = null

    if (props.changeableModelValue) {
      valueToShow.value = props.options.find(el => el[props.keyToSelect as keyof T] === modelValue.value)
    }
  }
)

watch(
  () => props.options,
  () => {
    if (props.showFirstItem && Array.isArray(props.options)) {
      valueToShow.value = props.options[0]
    }
  }
)

onBeforeMount(() => {
  if (modelValue.value && !props.getOnlyKey) {
    valueToShow.value = modelValue.value
  } else if (
    (!props.multiple && props.getOnlyKey && modelValue.value) ||
    modelValue.value === null
  ) {
    const value = props.options.filter((i: T) => modelValue.value == i[props.keyToSelect])
    valueToShow.value = value.length ? value[0] : null
  } else if (modelValue.value && props.multiple && props.getOnlyKey) {
    const value = props.options.filter((i: T) =>
      modelValue.value.includes(i[props.keyToSelect as keyof T])
    )
    valueToShow.value = value.length ? value : null
  } else if (!props.multiple && !props.label) {
    valueToShow.value = props.options[0]
  }
})

// onBeforeMount(() => {
//   if (props.multiple && !Array.isArray(modelValue)) modelValue.value = []
// })

const buttonWidth = computed(() => wrapper.value?.offsetWidth - 50)

const countValueToShow = computed(() => {
  if (props.multiple) {
    return modelValue.value?.length
  }
})

const maxWidthOfChip = computed(() => {
  const minWidthOfChip = 100
  if (countValueToShow.value) {
    const width = (buttonWidth.value - 50) / countValueToShow.value
    if (width < minWidthOfChip) {
      return minWidthOfChip + 'px'
    }
    return width + 'px'
  }
  return props.minimalWords ? minWidthOfChip + 'px' : 'auto'
})
const countOfVisibleItems = computed(() => {
  if (!valueToShow.value?.length) return undefined
  const maxCountOfVisibleItems = Math.floor((wrapper.value?.offsetWidth - 50) / 120)
  if (maxCountOfVisibleItems > valueToShow.value.length) return valueToShow.value.length
  else return Math.abs(maxCountOfVisibleItems)
})
const countOfInvisibleItems = computed(() => {
  if (!valueToShow.value?.length && !countOfVisibleItems.value) return undefined
  return (valueToShow.value?.length || 0) - countOfVisibleItems.value
})

onMounted(() => (countedVisibleItems.value = countOfVisibleItems.value))
onUpdated(() => (countedVisibleItems.value = countOfVisibleItems.value))
defineExpose({
  onClear
})
</script>
<template>
  <div ref="wrapper" class="relative w-full rounded-md text-sm">
    <button
      :class="[
        clearable ? 'pr-16' : 'pr-9',
        {
          'pb-0.5 pt-2.5': valueToShow && !multiple && label,
          'border-red': error,
          '!h-10': size === 'md'
        },
        disabledStyles,
        buttonBg,
      ]"
      :disabled="disabled"
      class="hover:border-blue focus-visible:outline-blue relative flex h-8 w-full items-center gap-2 rounded-md border border-solid py-1.5 pl-4 focus-visible:shadow-[0_0_0_4px#1B64B3] dark:hover:border-blue-700"
      @click="toggleOpen()"
    >
      <span
        v-if="valueToShow && !multiple && label"
        class="ease pointer-events-none absolute left-4 top-[6px] text-[10px] leading-none text-gray-500 transition-all duration-300"
      >
        {{ label }}
        <span v-if="required" class="text-red-500 dark:text-red-400">*</span>
      </span>
      <component :is="startIcon" v-if="startIcon" />

      <span :class="{ 'py-1': size === 'md' }" class="w-full text-start">
        <span v-if="valueToShow">
          <span v-if="!multiple" class="line-clamp-1 text-deepblue-900 dark:text-gray-200">
            <slot name="single-selected" :value="valueToShow">{{ valueToShow[keyToShow] }}</slot>
          </span>
          <span
            v-else-if="valueToShow"
            :class="{ 'py-0': size === 'md' }"
            class="flex gap-2 py-0.5 text-deepblue-900 dark:text-gray-200"
          >
            <a-chips
              v-for="(item, index) in valueToShow.slice(0, countOfVisibleItems)"
              :key="index"
              :style="{ 'max-width': maxWidthOfChip }"
              class="border-gray-500 text-deepblue-900 dark:text-gray-200"
              view="standard"
              @click.stop="canChipRemove ? onClearFromMulti(index) : (isOpen = true)"
            >
              <span class="truncate">{{ item[keyToShow] }}</span>
              <button v-if="canChipRemove" class="rounded-full bg-deepblue-100 dark:bg-gray-200/10">
                <a-icon-x-mark class="size-3 dark:text-gray-200" />
              </button>
            </a-chips>
            <a-chips
              v-if="(valueToShow?.length || 0) > countOfVisibleItems"
              class="border-blue-700 text-blue-700 dark:border-blue-500 dark:text-blue-500 whitespace-nowrap"
            >
              + {{ countOfInvisibleItems }}
            </a-chips>
          </span>
        </span>
        <span v-else class="body-400 text-gray-500">{{ label }}</span>
      </span>
      <button
        v-if="((multiple && valueToShow?.length) || (!multiple && valueToShow)) && clearable"
        class="absolute right-10 top-1/2 -translate-y-1/2 rounded-full bg-deepblue-100 dark:bg-gray-200/10"
        @click.stop="onClear"
      >
        <a-icon-x-mark class="!m-0 text-base dark:text-gray-200" />
      </button>
      <span
        :class="{ 'rotate-180': isOpen }"
        class="absolute right-4 top-1/2 -translate-y-1/2 text-base transition-transform"
      >
        <a-icon-chevron-down class="!m-0" style="color: #9da3ac" />
      </span>
    </button>
    <transition>
      <div
        v-if="isOpen"
        class="absolute z-20 mt-1 w-full rounded-md bg-white shadow-[0_1px_8px_0_rgba(139,146,156,0.3)] dark:bg-gray-900"
      >
        <slot :options="options" name="options">
          <ul class="select m-2 max-h-[250px] overflow-y-auto overflow-x-hidden">
            <li v-if="searchFromOut" class="p-1">
              <a-input v-model="search" :label="searchLabel" :size="size" clearable color="gray" />
            </li>
            <li v-if="searchable" class="p-1">
              <a-input
                v-model="searchValue"
                :label="searchLabel"
                :size="size"
                clearable
                color="gray"
              />
            </li>
            <li v-for="(option, index) in componentOptions" :key="index">
              <button
                v-if="!multiple"
                :class="{ 'dark:bg-[#E3E5E80D]': isEqual(valueToShow, option) }"
                :disabled="isEqual(valueToShow, option)"
                class="flex w-full items-center justify-between px-4 py-1.5 text-start hover:bg-deepblue-50 dark:bg-[#E3E5E80D]"
                @click="onSelect(option)"
              >
                <span>
                  <slot name="option" :option="option">{{ option[keyToShow] }}</slot>
                </span>
                <span v-if="isEqual(valueToShow, option)">
                  <a-icon-check style="color: #0070eb" />
                </span>
              </button>
              <button
                v-else
                :class="{
                  'dark:bg-[#E3E5E80D]':
                    valueToShow && valueToShow.some((item) => isEqual(option, item))
                }"
                class="flex w-full items-center justify-between px-4 py-1.5 text-start hover:bg-deepblue-50 dark:hover:bg-[#E3E5E80D]"
                @click="onSelect(option)"
              >
                <span>{{ option[keyToShow] }}</span>
                <span v-if="valueToShow && valueToShow.some((item) => isEqual(option, item))">
                  <a-icon-check style="color: #0070eb" />
                </span>
              </button>
            </li>
          </ul>
        </slot>
        <div
          v-if="props.hasButtons"
          class="-mb-2 flex w-full flex-col gap-2 rounded-md p-4 shadow-[0_-2px_8px_0#9DA3AC3D] xl:flex-row"
        >
          <slot name="buttons">
            <a-button view="outline" variant="gray" block @click="onClear"> Сбросить </a-button>
            <a-button block @click="emits('onSubmit', modelValue)"> Применить </a-button>
          </slot>
        </div>
      </div>
    </transition>
  </div>
</template>

<style lang="scss" scoped>
.select {
  &::-webkit-scrollbar {
    border-radius: 2px 0 0 0;
    width: 5px;
  }

  /* Track */
  &::-webkit-scrollbar-track {
    background: #f1f1f1;
  }

  /* Handle */
  &::-webkit-scrollbar-thumb {
    background: #888;
  }
}
</style>
