<!--
Champs autocomplete dédié à la recherche d'une Resource dans l'api
Exemple : on souhaite lister les lieux d'une structure
<UiInputAutocompleteApiResources
          v-model="entity.place"
          field="place"
          :model="PlaceSearchItem"
          listValue="id"
          listLabel="name"
        />

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

<template>
  <main>
    <UiInputAutocomplete
      :model-value="modelValue"
      :field="field"
      :label="label"
      :items="items"
      :item-value="listValue"
      :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"
      :readonly="readonly"
      :clearable="true"
      @update:model-value="onUpdateModelValue"
      @update:search="onUpdateSearch"
    />
  </main>
</template>

<script setup lang="ts">
import type {PropType, ComputedRef, Ref, WatchStopHandle} from 'vue'
import { computed } from 'vue'
import * as _ from 'lodash-es'
import { useEntityFetch } from '~/composables/data/useEntityFetch'
import Query from '~/services/data/Query'
import OrderBy from '~/services/data/Filters/OrderBy'
import {FETCHING_STATUS, 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 type ApiModel from "~/models/ApiModel";
import PlaceSearchItem from "~/models/Custom/Search/PlaceSearchItem";

const props = defineProps({
  /**
   * Valeur actives (tableau si multiple ou ID seule si choix unique)
   */
  modelValue: {
    type: [Array, Number],
    required: false,
    default: null,
  },
  /**
   * API Resource qui sera fetch
   */
  model: {
    type: Function as PropType<() => typeof ApiModel>,
    required: true,
  },
  /**
   * Filtres à transmettre à la source de données
   */
  query: {
    type: Object as PropType<typeof Query | null>,
    required: false,
    default: 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',
  },
  /**
   * Nom de la propriété servant à générer les values dans la list
   */
  listValue: {
    type: String,
    required: false,
    default: null,
  },
  /**
   * Nom de la propriété servant à générer les labels dans la list
   */
  listLabel: {
    type: String,
    required: false,
    default: null,
  },
})

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

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

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


/**
 * Query transmise à l'API lors de l'initialisation afin de récupérer les items actifs
 */
const queryActive = computed(() => {
  return new Query(
      new OrderBy(props.listLabel, ORDER_BY_DIRECTION.ASC),
      new PageFilter(ref(1), ref(20)),
      new InArrayFilter(props.listValue, activeIds),
    )
  }
)

/**
 * On commence par fetcher les models déjà actifs, pour affichage des labels correspondant
 */
const {
  data: collectionActive,
  status: statusActive,
  refresh: refreshActive
} = fetchCollection(props.model, null, queryActive.value)

const unwatch: WatchStopHandle = watch(activeIds, ()=>{
  refreshActive()
})

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

/**
 * Query transmise à l'API lors des changements de filtre de recherche
 */
const querySearch = new Query(
  new OrderBy(props.listLabel, ORDER_BY_DIRECTION.ASC),
  new PageFilter(ref(1), ref(20)),
  new SearchFilter(props.listLabel, searchFilter, SEARCH_STRATEGY.IPARTIAL),
)
/**
 * On fetch les résultats correspondants à la recherche faite par l'utilisateur
 */
const {
  data: collectionSearch,
  status: statusSearch,
  refresh: refreshSearch,
} = fetchCollection(props.model, null, querySearch)

//Le pending global dépend des deux recherche (actif, et globale)
const pending = computed(() => statusSearch.value == FETCHING_STATUS.PENDING || statusActive.value == FETCHING_STATUS.PENDING)

/**
 * Génère un ListItem à partir des props
 * @param searchItem
 */
const item = (searchItem): ListItem => {
  return {
    id: searchItem[props.listValue],
    title: searchItem[props.listLabel]
      ? searchItem[props.listLabel]
      : `(${i18n.t('missing_value')})`,
  }
}

/**
 * Contenu de la liste autocomplete : Les items actifs + les items correspondants à la recherche
 */
const items: ComputedRef<Array<ListItem>> = computed(() => {
  if (pending.value || !(collectionActive.value && collectionSearch.value)) {
    return []
  }

  const activeItems: ListItem[] = collectionActive.value.items.map(item)

  const searchedItems: ListItem[] = collectionSearch.value.items
    .map(item)
    .filter(
      (item) =>
        !collectionActive.value!.items.find((other) => other[props.listValue] === item[props.listValue]),
    )
  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 = 400

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

/**
 * La recherche textuelle a changé.
 * @param event
 */
const onUpdateSearch = (event: string) => {
  let search = true
  if(searchFilter.value === null){
    search = false
  }

  searchFilter.value = event
  if(search){
    refreshDebounced()
  }
}

// ### Events
const emit = defineEmits(['update:model-value'])
/**
 * Quand un item est sélectionné
 * @param event
 */
const onUpdateModelValue = (event: Array<number>) => {
  if (props.clearSearchAfterUpdate) {
    searchFilter.value = ''
  }
  emit('update:model-value', event)
}

// Nettoyer les données lors du démontage du composant
onBeforeUnmount(() => {
  // Nettoyer les références du store si nécessaire
  if (import.meta.client) {
    clearNuxtData('/^' + props.model.entity + '_many_/')
    useRepo(props.model).flush()
  }
  unwatch()
})
</script>

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