import { AnyJson, DataProviderArgs } from '~/types/interfaces'
import { DENORMALIZER_TYPE, HTTP_METHOD, QUERY_TYPE } from '~/types/enums'
import { processors } from '~/services/data/processor/_import'
import UrlBuilder from '~/services/connection/urlBuilder'
import Serializer from '~/services/serializer/serializer'
import BaseDataManager from '~/services/data/baseDataManager'
import { hooksProvider } from '~/services/data/hooks/hookProvider/_import'

/**
 * Le DataProvider a pour rôle de fournir des données issues de l'API
 *
 * Pour cela, le DataProvider envoie une requête GET, récupérer la réponse de l'API,
 * puis la désérialiser.
 * Enfin, il va itérer sur les différents Processor disponibles, jusqu'à trouver
 * un processeur adapté à la requête. Ce processeur va mettre en forme la réponse qui est enfin
 * retournée par le DataProvider.
 */
class DataProvider extends BaseDataManager {
  protected progress: boolean = false
  protected hooks = hooksProvider;
  protected defaultArguments: object = {
    type: QUERY_TYPE.MODEL,
    showProgress: false
  }

  /**
   * Exécute la requête et retourne la réponse désérialisée
   * @param {DataDeleterArgs} queryArguments
   */
  protected async _invoke (queryArguments:DataProviderArgs): Promise<any> {
    // build the url according to the url args
    const url = UrlBuilder.build(queryArguments)

    // send the GET request to the api and retrieve the response
    const response = await DataProvider.request(url, HTTP_METHOD.GET, queryArguments)

    // deserialize the response
    const data = await Serializer.denormalize(response, DENORMALIZER_TYPE.HYDRA)

    // post-process the data with the first supported processor
    return await this.process(data, queryArguments)
  }

  /**
   * Find the first supported post data processor, and process the data with it
   * @param {AnyJson} data
   * @param {DataProviderArgs} args
   */
  public async process (data: AnyJson, args: DataProviderArgs) {
    const postProcessor = this.getProcessor(args)
    if(!postProcessor) throw new Error('A processor need to be defined')
    return await postProcessor.process(data)
  }

  /**
   * Iterate over the available data processors and return an instance of
   * the first one that support the given args
   * @param {DataProviderArgs} args
   */
  private getProcessor (args: DataProviderArgs): any {
    for (const processor of processors) {
      if (processor.support(args)) {
        // eslint-disable-next-line new-cap
        return new processor(this.ctx, args)
      }
    }
  }
}

export default DataProvider
