<!--
Champs autocomplete dédié à la recherche des access 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"
        :isLoading="pending"
        :multiple="multiple"
        hide-no-data
        :chips="chips"
        :auto-select-first="false"
        prependIcon="fas fa-magnifying-glass"
        :return-object="false"
        :variant="variant"
        @update:model-value="onUpdateModelValue"
        @update:search="onUpdateSearch"
    />
  </main>
</template>

<script setup lang="ts">
import type {PropType} from "@vue/runtime-core";
import {computed} from "@vue/reactivity";
import type {ComputedRef, Ref} from "@vue/reactivity";
import type {AnyJson, AssociativeArray} from "~/types/data";
import {useEntityFetch} from "~/composables/data/useEntityFetch";
import Access from "~/models/Access/Access";
import {useEntityManager} from "~/composables/data/useEntityManager";
import ArrayUtils from "~/services/utils/arrayUtils";
import * as _ from 'lodash-es'

const props = defineProps({
  /**
   * v-model
   */
  modelValue: {
    type: [Object, Array],
    required: false,
    default: null
  },
  /**
   * Filtres à transmettre à la source de données
   */
  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: 'filled'
  }
})

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

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

/**
 * Génère un AccessListItem à partir d'un Access
 * @param access
 */
const accessToItem = (access: Access): AccessListItem => {
  return {
    id: access.id,
    title: access.person ? `${access.person.givenName} ${access.person.name}` : i18n.t('unknown')
  }
}

const getFromStore = (id: number) => {
  return em.find(Access, id)
}

const initialized: Ref<boolean> = ref(false)

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

/**
 * Query transmise à l'API lors des changements de filtre de recherche
 */
const query: ComputedRef<AnyJson> = computed(() => {
  let q: AnyJson = {'groups[]': 'access_people_ref'}

  if (!initialized.value && props.modelValue) {
    if (Array.isArray(props.modelValue) && props.modelValue.length > 0) {
      q['id[in]'] = props.modelValue.join(',')
    } else {
      q['id[in]'] = props.modelValue
    }
    return q
  }

  if (nameFilter.value !== null) {
    q['fullname'] = nameFilter.value
  }

  return q
})

/**
 * On commence par fetcher les accesses déjà actifs, pour affichage des noms
 */
const { data: collection, pending, refresh } = await fetchCollection(
    Access,
    null,
    query
)
initialized.value = true

/**
 * Contenu de la liste autocomplete
 */
const items: ComputedRef<Array<AccessListItem>> = computed(() => {
  let items = props.modelValue.map(getFromStore).map(accessToItem)

  if (!collection.value) {
    return []
  }

  //@ts-ignore
  const fetchedItems = collection.value.items.map(accessToItem)

  for (let item of fetchedItems) {
    if (!items.some((existingItem: AccessListItem) => existingItem.id === item.id)) {
      items.push(item)
    }
  }

  return ArrayUtils.sortObjectsByProp(items, 'title') as Array<AccessListItem>
})


/**
 * 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 refresh();
}, 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;
  }
</style>
