<!--
Champs autocomplete dédié à la recherche des Accesses d'une structure

@see https://vuetifyjs.com/en/components/autocompletes/#usage
-->

<template>
  <main>
    <UiInputAutocomplete
      :model-value="modelValue"
      :field="field"
      :label="label"
      :items="items"
      item-value="id"
      :is-loading="pending"
      :multiple="multiple"
      hide-no-data
      :chips="chips"
      :closable-chips="true"
      :auto-select-first="false"
      prepend-inner-icon="fas fa-magnifying-glass"
      :return-object="false"
      :variant="variant"
      :class="pending || pageStore.loading ? 'hide-selection' : ''"
      @update:model-value="onUpdateModelValue"
      @update:search="onUpdateSearch"
    />
  </main>
</template>

<script setup lang="ts">
import type { PropType, ComputedRef, Ref } from 'vue'
import { computed } from 'vue'
import * as _ from 'lodash-es'
import type { AssociativeArray } from '~/types/data'
import { useEntityFetch } from '~/composables/data/useEntityFetch'
import Access from '~/models/Access/Access'
import Query from '~/services/data/Query'
import OrderBy from '~/services/data/Filters/OrderBy'
import { ORDER_BY_DIRECTION, SEARCH_STRATEGY } from '~/types/enum/data'
import PageFilter from '~/services/data/Filters/PageFilter'
import InArrayFilter from '~/services/data/Filters/InArrayFilter'
import SearchFilter from '~/services/data/Filters/SearchFilter'
import UserSearchItem from '~/models/Custom/Search/UserSearchItem'

const props = defineProps({
  /**
   * v-model, ici les ids des Access sélectionnés
   */
  modelValue: {
    type: [Object, Array],
    required: false,
    default: null,
  },
  /**
   * Filtres à transmettre à la source de données
   * TODO: voir si à adapter maintenant que les filtres sont des objets Query
   */
  filters: {
    type: Object as PropType<Ref<AssociativeArray>>,
    required: false,
    default: ref(null),
  },
  /**
   * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
   * - Utilisé par la validation
   * - Laisser null si le champ ne s'applique pas à une entité
   */
  field: {
    type: String,
    required: false,
    default: null,
  },
  /**
   * Label du champ
   * Si non défini, c'est le nom de propriété qui est utilisé
   */
  label: {
    type: String,
    required: false,
    default: null,
  },
  /**
   * Définit si le champ est en lecture seule
   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-readonly
   */
  readonly: {
    type: Boolean,
    required: false,
  },
  /**
   * Autorise la sélection multiple
   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-multiple
   */
  multiple: {
    type: Boolean,
    default: false,
  },
  /**
   * Rends les résultats sous forme de puces
   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-chips
   */
  chips: {
    type: Boolean,
    default: false,
  },
  /**
   * Closes the menu and clear the current search after the selection has been updated
   */
  clearSearchAfterUpdate: {
    type: Boolean,
    default: false,
  },
  /**
   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
   */
  variant: {
    type: String as PropType<
      | 'filled'
      | 'outlined'
      | 'plain'
      | 'underlined'
      | 'solo'
      | 'solo-inverted'
      | 'solo-filled'
      | undefined
    >,
    required: false,
    default: 'outlined',
  },
})

/**
 * Element de la liste autocomplete
 */
interface UserListItem {
  id: number | string
  title: string
}

const { fetchCollection } = useEntityFetch()
const i18n = useI18n()
const pageStore = usePageStore()

/**
 * Génère un AccessListItem à partir d'un Access
 * @param userSearchItem
 */
const accessToItem = (userSearchItem: UserSearchItem): UserListItem => {
  return {
    id: userSearchItem.id,
    title: userSearchItem.fullName
      ? userSearchItem.fullName
      : `(${i18n.t('missing_name')})`,
  }
}

/**
 * Saisie de l'utilisateur utilisée pour filtrer la recherche
 */
const nameFilter: Ref<string | null> = ref(null)

const activeIds = computed(() => {
  if (Array.isArray(props.modelValue)) {
    return props.modelValue
  }
  if (props.modelValue !== null && typeof props.modelValue === 'object') {
    return [props.modelValue.id]
  }
  return []
})

const queryActive = new Query(
  new OrderBy('fullName', ORDER_BY_DIRECTION.ASC),
  new PageFilter(ref(1), ref(20)),
  new InArrayFilter('id', activeIds),
)

const { data: collectionActive, pending: pendingActive } = fetchCollection(
  UserSearchItem,
  null,
  queryActive,
)

/**
 * Query transmise à l'API lors des changements de filtre de recherche
 */
const querySearch = new Query(
  new OrderBy('fullName', ORDER_BY_DIRECTION.ASC),
  new PageFilter(ref(1), ref(20)),
  new SearchFilter('fullName', nameFilter, SEARCH_STRATEGY.IPARTIAL),
)

/**
 * On commence par fetcher les accesses déjà actifs, pour affichage des noms
 */
const {
  data: collectionSearch,
  pending: pendingSearch,
  refresh: refreshSearch,
} = fetchCollection(UserSearchItem, null, querySearch)

const pending = computed(() => pendingSearch.value || pendingActive.value)

/**
 * Contenu de la liste autocomplete
 */
const items: ComputedRef<Array<UserListItem>> = computed(() => {
  if (pending.value || !(collectionActive.value && collectionSearch.value)) {
    return []
  }

  const activeItems: UserListItem[] =
    collectionActive.value.items.map(accessToItem)
  const searchedItems: UserListItem[] = collectionSearch.value.items
    .map(accessToItem)
    .filter(
      (item) =>
        !collectionActive.value!.items.find((other) => other.id === item.id),
    )

  return activeItems.concat(searchedItems)
})

/**
 * Délai entre le dernier caractère saisi et la requête de vérification de la mise à jour des résultats (en ms)
 */
const inputDelay = 600

/**
 * Version debounced de la fonction refresh
 * @see https://docs-lodash.com/v4/debounce/
 */
const refreshDebounced: _.DebouncedFunc<() => void> = _.debounce(async () => {
  await refreshSearch()
}, inputDelay)

// ### Events
const emit = defineEmits(['update:model-value'])

/**
 * La recherche textuelle a changé.
 * @param event
 */
const onUpdateSearch = (event: string) => {
  nameFilter.value = event
  refreshDebounced()
}

const onUpdateModelValue = (event: Array<number>) => {
  if (props.clearSearchAfterUpdate) {
    nameFilter.value = ''
  }
  emit('update:model-value', event)
}
</script>

<style scoped lang="scss">
.v-autocomplete {
  min-width: 350px;
}

.hide-selection {
  /**
      On cache le contenu au chargement en attendant de résoudre le bug qui fait
      que ce sont les ids ou les IRIs qui s'affichent le temps du chargement
   */
  :deep(.v-chip__content) {
    color: transparent !important;
  }
}
</style>
