import {
  computed,
  defineComponent,
  onMounted,
  ref,
  watch,
  type PropType,
  type Ref,
} from 'vue'
import { ArkUiConstants } from '../../composables/use-ark-ui'
import useClickOutside from '../../composables/use-click-outside'
import type ApiInterface from '../../types/api-interface'
import type DataProviderInterface from '../../types/data-provider-interface'
import defineTemplate, { DEFAULT_SHOW_ITEMS_COUNT } from './template'
import type { SelectSlotParams } from './types'

import { type paramsSerializationOptions } from '../../composables/use-endless-scroll-api'
import { createQueryCommaString, createQueryString } from '../../utils'
import './styles.scss'

export function defineEnumSelect<T extends { id: string, name: string }>() {
  return defineComponent({
    props: {
      /**
       * Опция копирования значения
       */
      isWithCopy: {
        type: Boolean,
        default: false,
      },

      api: {
        type: Object as PropType<ApiInterface>,
        default: undefined,
      },
      dataProvider: {
        type: Object as PropType<DataProviderInterface>,
        default: undefined,
      },
      appearance: {
        type: String as PropType<'filter'|'form'>,
        default: 'filter',
      },
      resetable: {
        type: Boolean,
        default: false,
      },
      resetOnQueryParamsChange: {
        type: Boolean,
        default: false,
      },
      disabled: {
        type: Boolean,
        default: false,
      },
      endpoint: {
        type: String,
        required: true,
      },
      errors: {
        type: Array as PropType<string[]>,
        default: () => [],
      },
      itemConverter: {
        type: Function as PropType<(item: any) => T>,
        default: (i: any) => i,
      },
      itemSlot: {
        type: Function as PropType<(item: T, params: SelectSlotParams<T>) => JSX.Element>,
      },
      modelValue: {
        type: Object as PropType<T|undefined>,
        default: undefined,
      },
      onValueChange: {
        type: Function as PropType<(v: T|undefined) => void>,
        default: () => () => {
          // do nothing
        },
      },
      params: {
        type: Object as PropType<{ [code: string] : string|string[] }>,
        default: () => ({}),
      },
      placeholder: {
        type: String,
        default: '',
      },
      preselected: {
        type: Boolean,
        default: false,
      },
      searchKey: {
        type: String,
        default: 'name',
      },
      size: {
        type: String as PropType<'M'|'S'>,
      },
      title: {
        type: String,
      },
      paramsSerialization: {
        type: String as PropType<paramsSerializationOptions>,
        default: 'default',
      },
      additionalItems: {
        type: Array as PropType<T[]>,
      },
      showItemsCount: {
        type: Number,
        default: DEFAULT_SHOW_ITEMS_COUNT,
      },
      showDescriptionId: {
        type: Boolean,
        default: false,
      },
    },

    setup(props) {
      const api = props.api || ArkUiConstants.api

      if (!api && !props.dataProvider) throw new Error('API is not defined')

      const isOpened = ref(false)
      const triggerDown = ref(null) as unknown as Ref<HTMLElement>
      const triggerUp = ref(null) as unknown as Ref<HTMLElement>
      const canBeLoaded = ref(false)
      const root = ref(null) as unknown as Ref<HTMLElement>
      const select = ref(null) as unknown as Ref<HTMLElement>
      const isLoadingPrev = ref(false)
      const isLoadingNext = ref(false)

      const value = computed(() => props.modelValue as T)

      const queryParams = computed((): typeof props.params => {
        const params = { ...props.params } // Де-структуризация ломает реактивность
        return params
      })

      const clear = () => {
        if (props.onValueChange) props.onValueChange(undefined)
      }

      const items: Ref<T[]> = ref([])

      const checkParamsSerialization = async (): Promise<any> => {
        if (!api) throw new Error('API is not defined')
        let resp
        switch (props.paramsSerialization) {
          case 'default':
            resp = (await api.get(props.endpoint, { params: queryParams.value })).data
            break
          case 'custom':
            resp = (await api.get(props.endpoint + createQueryString(queryParams.value))).data
            break
          case 'comma':
            resp = (await api.get(props.endpoint + createQueryCommaString(queryParams.value))).data
            break
          default:
            break
        }
        return resp
      }

      const load = async () => {
        const response = props.dataProvider
          ? await props.dataProvider.get({ params: queryParams.value })
          : await checkParamsSerialization()

        items.value = response.map((i: any) => props.itemConverter(i))
      }

      if (props.preselected) {
        onMounted(async () => {
          await load()
          canBeLoaded.value = true

          if (props.onValueChange) props.onValueChange(items.value[0])
        })
      }

      watch(() => props.params, () => {
        if (props.resetOnQueryParamsChange) clear()
        // мгновенная перезагрузка происходит только в том случае, если селект раскрыт, иначе она будет выполена отложенно при раскрытии
        if (isOpened.value) {
          isLoadingNext.value = true
          isLoadingPrev.value = true

          load().finally(() => {
            isLoadingNext.value = false
            isLoadingPrev.value = false
          })
          return
        }

        items.value = []
      })

      watch(isOpened, async () => {
        if (isOpened.value && items.value.length === 0) {
          isLoadingNext.value = true
          await load()
          isLoadingNext.value = false
          canBeLoaded.value = true
        }
      })

      useClickOutside(select, () => { isOpened.value = false })

      const toggleOpened = () => {
        isOpened.value = !isOpened.value
      }

      const toggleItem = (item: T) => {
        if (value.value?.id === item.id) {
          if (props.onValueChange) props.onValueChange(undefined)
        } else if (props.onValueChange) props.onValueChange(item)

        toggleOpened()
      }

      const isSelected = (item: T) => value.value?.id === item.id

      const SelectTemplate = defineTemplate<T>()

      return (): JSX.Element => {
        const templateProps = {
          clear,
          isWithCopy: props.isWithCopy,
          disabled: props.disabled,
          errors: props.errors,
          isLoadingNext: isLoadingNext.value,
          isLoadingPrev: isLoadingPrev.value,
          isOpened: isOpened.value,
          isSelected,
          items: items.value,
          itemSlot: props.itemSlot,
          modelValue: value.value ? [value.value] : [],
          placeholder: props.placeholder,
          resetable: props.resetable,
          root,
          select,
          title: props.title,
          toggleItem,
          toggleOpened,
          topItems: [],
          triggerUp,
          triggerDown,
          isSingle: true,
          appearance: props.appearance,
          additionalItems: props.additionalItems,
          size: props.size,
          showItemsCount: props.showItemsCount,
          showDescriptionId: props.showDescriptionId,
        }

        return <SelectTemplate {...templateProps}/>
      }
    },
  })
}
export default defineEnumSelect<{ id: string, name: string }>()
