import PreloadManager from "./cache/PreloadManager";
import { DatasetStrategy } from "./loadstrategy/DatasetStrategy";
import { CancelPaginationResponse, showAskCancelPaginationPopup } from './helpers/DataUnitDataLoaderHelper';
export default class DataUnitDataLoader {
  constructor() { }
  static async debounce(dataUnit, callBack) {
    const duName = dataUnit.name;
    if (DataUnitDataLoader._debouncingTimeouts[duName]) {
      clearTimeout(DataUnitDataLoader._debouncingTimeouts[duName]);
      delete DataUnitDataLoader._debouncingTimeouts[duName];
    }
    DataUnitDataLoader._debouncingTimeouts[duName] = setTimeout(() => {
      delete DataUnitDataLoader._debouncingTimeouts[duName];
      callBack();
    }, 100);
  }
  static async loadData(dataUnit, request) {
    return new Promise((resolve, reject) => {
      DataUnitDataLoader.debounce(dataUnit, () => {
        PreloadManager.loadData(dataUnit, request, this.loadFromServer)
          .then(resp => resolve(resp))
          .catch(reason => reject(reason));
      });
    });
  }
  static getCachedRecords(dataUnit) {
    return PreloadManager.getCachedRecords(dataUnit);
  }
  static async loadFromServer(dataUnit, request, loadingInfo) {
    try {
      //Registramos a request com as informações de carga para determinarmos a última.
      //Caso o usuário inicie outro refresh antes de terminar, devemos ignorar o resultado.
      loadingInfo = DataUnitDataLoader.registryLoading(dataUnit, loadingInfo);
      const loadResult = await DataUnitDataLoader.callLoader(dataUnit, request, loadingInfo);
      if (loadResult == undefined) {
        //Essa request foi originada de refresh antigo. Será ignorada;
        return;
      }
      const loadedRecords = loadResult.records;
      const paginationInfo = DataUnitDataLoader.buildPaginationInfo(loadedRecords.length, request, loadResult.loadingInfo);
      const records = paginationInfo == undefined ? loadedRecords : loadedRecords.slice(0, paginationInfo.lastRecord);
      return Promise.resolve({ records, paginationInfo });
    }
    catch (error) {
      console.error(error);
      return Promise.reject(error);
    }
  }
  static async callLoader(dataUnit, request, requestLoadingInfo, dataLoader) {
    if (dataLoader == undefined) {
      dataLoader = new DatasetStrategy();
    }
    const result = await dataLoader.load(dataUnit, request, requestLoadingInfo);
    const responseLoadingInfo = result.loadingInfo;
    if (DataUnitDataLoader.isOldRequest(dataUnit, responseLoadingInfo))
      return Promise.resolve(undefined);
    this.handleCache(dataLoader, responseLoadingInfo, dataUnit, result.records);
    if (dataUnit.cancelPagination)
      return this.handlePaginationCanceled(dataUnit, result);
    //Não existem mais páginas a serem carregadas
    if (this.canFinishPagination(dataUnit, responseLoadingInfo))
      return Promise.resolve(result);
    const newRequest = Object.assign(Object.assign({}, request), { offset: responseLoadingInfo.count });
    let newLoadingInfo = this.buildNewLoadingInfo(responseLoadingInfo);
    if (this.canProceedPagination(responseLoadingInfo))
      return this.recallLoader(dataUnit, newRequest, newLoadingInfo, dataLoader, result);
    const confirm = await showAskCancelPaginationPopup();
    if (confirm === CancelPaginationResponse.CANCEL)
      return this.handlePaginationCanceled(dataUnit, result);
    if (confirm === CancelPaginationResponse.LOAD_ALL) {
      newLoadingInfo = Object.assign(Object.assign({}, newLoadingInfo), { loadAllRecords: true });
    }
    newLoadingInfo = Object.assign(Object.assign({}, newLoadingInfo), { lastRowLimitAsked: newLoadingInfo.count });
    return this.recallLoader(dataUnit, newRequest, newLoadingInfo, dataLoader, result);
  }
  static buildNewLoadingInfo(responseLoadingInfo) {
    let newLoadingInfo = Object.assign(Object.assign({}, responseLoadingInfo), { pageNumber: (responseLoadingInfo.pageNumber || 0) + 1, quiet: true });
    return newLoadingInfo;
  }
  static handleCache(dataLoader, responseLoadingInfo, dataUnit, records) {
    const recreateCache = !dataLoader.canSlice() || responseLoadingInfo.count === 0;
    PreloadManager.cacheRecords(dataUnit, records, recreateCache, responseLoadingInfo.loadingInProgress);
  }
  static recallLoader(dataUnit, newRequest, newLoadingInfo, dataLoader, result) {
    this.callLoader(dataUnit, newRequest, newLoadingInfo, dataLoader)
      .then(result => DataUnitDataLoader.afterLoadingPage(dataUnit, result.loadingInfo))
      .catch(reason => console.error(reason));
    return Promise.resolve(result);
  }
  static canFinishPagination(dataUnit, responseLoadingInfo) {
    return !PreloadManager.isCacheEnabled(dataUnit) || !responseLoadingInfo.loadingInProgress;
  }
  static canProceedPagination(info) {
    const { count, askRowsLimit, loadAllRecords, lastRowLimitAsked } = info;
    if (loadAllRecords)
      return true;
    if (!count || !askRowsLimit)
      return true;
    const askRowLimitNumber = Number(askRowsLimit);
    if (count < askRowLimitNumber)
      return true;
    const nextLimitToAsk = askRowLimitNumber + (lastRowLimitAsked ? lastRowLimitAsked : 0);
    return count < nextLimitToAsk;
  }
  static handlePaginationCanceled(dataUnit, result) {
    dataUnit.cancelPagination = false;
    let loadingInfo = result.loadingInfo;
    loadingInfo = Object.assign(Object.assign({}, loadingInfo), { total: loadingInfo.count, loadingInProgress: false, pagerId: undefined, needReload: loadingInfo.hasLocalSorting });
    const loadDataResult = { records: result.records, loadingInfo };
    return Promise.resolve(loadDataResult);
  }
  static afterLoadingPage(dataUnit, loadingInfo) {
    PreloadManager.setLoadingStatus(dataUnit, loadingInfo.loadingInProgress);
    const dataUnitPagination = dataUnit.getPaginationInfo();
    if (dataUnitPagination == undefined) {
      return;
    }
    const count = loadingInfo.count;
    if (loadingInfo.loadingInProgress) {
      dataUnit.updatePagination(Object.assign(Object.assign({}, dataUnitPagination), { count, loadingInProgress: true }));
      return;
    }
    if (loadingInfo.needReload) {
      //Ir para a primeira página, faz com que o loadData seja chamado novamente
      dataUnit.gotoPage(0);
      return;
    }
    dataUnit.updatePagination(Object.assign(Object.assign({}, dataUnitPagination), { total: count, count, loadingInProgress: false }));
  }
  static registryLoading(dataUnit, loadingInfo) {
    if (loadingInfo == undefined) {
      loadingInfo = { requestTime: new Date().getTime(), count: 0 };
    }
    DataUnitDataLoader._requestTimeByDataUnit.set(dataUnit.name, loadingInfo.requestTime);
    return loadingInfo;
  }
  static isOldRequest(dataUnit, loadingInfo) {
    return DataUnitDataLoader._requestTimeByDataUnit.get(dataUnit.name) > loadingInfo.requestTime;
  }
  static buildPaginationInfo(pageSize, request, loadingInfo) {
    let { limit, offset } = request;
    if (!limit) {
      return undefined;
    }
    if (loadingInfo.pageNumber === 1) {
      // Quando o loader não é capaz de fatiar os registros
      // ele pode decidir por uma carga completa, o que faz o
      // controle de paginação voltar pra primeira página.
      offset = 0;
    }
    const { total, count, loadingInProgress, askRowsLimit } = loadingInfo;
    const firstRecord = (count === 0 || pageSize === 0) ? 0 : offset + 1;
    const lastRecord = offset + Math.min(pageSize, limit);
    return {
      total,
      count,
      lastRecord,
      firstRecord,
      currentPage: offset / limit,
      hasMore: lastRecord < count || loadingInProgress,
      askRowsLimit
    };
  }
}
DataUnitDataLoader._requestTimeByDataUnit = new Map();
DataUnitDataLoader._debouncingTimeouts = {};
