import type { AsyncData } from '#app'
import type { ComputedRef, Ref } from 'vue'
import { v4 as uuid4 } from 'uuid'
import type {
  AsyncDataExecuteOptions,
  AsyncDataRequestStatus,
} from '#app/composables/asyncData'
import { useEntityManager } from '~/composables/data/useEntityManager'
import ApiResource from '~/models/ApiResource'
import type { Collection } from '~/types/data'
import Query from '~/services/data/Query'

interface useEntityFetchReturnType {
  fetch: (
    model: typeof ApiResource,
    id: number,
  ) => AsyncData<ApiResource | null, Error | null>

  fetchCollection: (
    model: typeof ApiResource,
    parent?: ApiResource | null,
    query?: Query | null,
  ) => {
    data: ComputedRef<Collection | null>
    pending: Ref<boolean>
    refresh: (
      opts?: AsyncDataExecuteOptions,
    ) => Promise<ComputedRef<Collection> | null>
    error: Ref<Error | null>
    status: Ref<AsyncDataRequestStatus>
  }

  getRef: <T extends ApiResource>(
    model: new () => T,
    id: Ref<number | null>,
  ) => ComputedRef<null | T>
}

// TODO: améliorer le typage des fonctions sur le modèle de getRef
export const useEntityFetch = (
  lazy: boolean = false,
): useEntityFetchReturnType => {
  const { em } = useEntityManager()

  const fetch = (model: typeof ApiResource, id: number) =>
    useAsyncData(
      model.entity + '_' + id + '_' + uuid4(),
      () => em.fetch(model, id, true),
      { lazy },
    )

  const fetchCollection = (
    model: typeof ApiResource,
    parent: ApiResource | null = null,
    query: Query | null = null,
  ) => {
    const { data, pending, refresh, error, status } = useAsyncData(
      model.entity + '_many_' + uuid4(),
      () => em.fetchCollection(model, parent, query),
      { lazy, deep: true },
    )

    return {
      data: computed(() => (data.value !== null ? data.value.value : null)),
      pending,
      refresh,
      error,
      status,
    }
  }

  const getRef = <T extends ApiResource>(
    model: new () => T,
    id: Ref<number | null>,
  ): ComputedRef<T | null> => {
    return computed(() => (id.value ? (em.find(model, id.value) as T) : null))
  }

  return { fetch, fetchCollection, getRef }
}
