/* eslint-disable no-console */
/* eslint-disable max-len */
/* eslint-disable object-curly-newline */
import { isJSFunction } from '@alilc/lowcode-types';
import { transformArrayToMap, transformStringToFunction } from './common';
import { jsonp, request, get, post } from './request';
import logger from './logger';
import { DataSource, DataSourceItem, IRendererAppHelper } from '../types';

const DS_STATUS = {
  INIT: 'init',
  LOADING: 'loading',
  LOADED: 'loaded',
  ERROR: 'error',
};

type DataSourceType = 'fetch' | 'jsonp';

/**
 * do request for standard DataSourceType
 * @param {DataSourceType} type type of DataSourceItem
 * @param {any} options
 */
export function doRequest(type: DataSourceType, options: any) {
  // eslint-disable-next-line prefer-const
  let { uri, url, method = 'GET', headers, params, ...otherProps } = options;
  otherProps = otherProps || {};
  if (type === 'jsonp') {
    return jsonp(uri, params, otherProps);
  }

  if (type === 'fetch') {
    switch (method.toUpperCase()) {
      case 'GET':
        return get(uri, params, headers, otherProps);
      case 'POST':
        return post(uri, params, headers, otherProps);
      default:
        return request(uri, method, params, headers, otherProps);
    }
  }

  logger.log(`Engine default dataSource does not support type:[${type}] dataSource request!`, options);
}

// TODO: according to protocol, we should implement errorHandler/shouldFetch/willFetch/requestHandler and isSync controll.
export class DataHelper {
  /**
   * host object that will be "this" object when excuting dataHandler
   *
   * @type {*}
   * @memberof DataHelper
   */
  host: any;

  /**
   * data source config
   *
   * @type {DataSource}
   * @memberof DataHelper
   */
  config: DataSource;

  /**
   * a parser function which will be called to process config data
   * which eventually will call common/utils.processData() to process data
   * (originalConfig) => parsedConfig
   * @type {*}
   * @memberof DataHelper
   */
  parser: any;

  /**
   * config.list
   *
   * @type {any[]}
   * @memberof DataHelper
   */
  ajaxList: any[];

  ajaxMap: any;

  dataSourceMap: any;

  appHelper: IRendererAppHelper;

  constructor(comp: any, config: DataSource, appHelper: IRendererAppHelper, parser: any) {
    this.host = comp;
    this.config = config || {};
    this.parser = parser;
    this.ajaxList = config?.list || [];
    this.ajaxMap = transformArrayToMap(this.ajaxList, 'id');
    this.dataSourceMap = this.generateDataSourceMap();
    this.appHelper = appHelper;
  }

  // 更新config，只会更新配置，状态保存；
  updateConfig(config = {}) {
    this.config = config as DataSource;
    this.ajaxList = (config as DataSource)?.list || [];
    const ajaxMap: any = transformArrayToMap(this.ajaxList, 'id');
    // 删除已经移除的接口
    Object.keys(this.ajaxMap).forEach((key) => {
      if (!ajaxMap[key]) {
        delete this.dataSourceMap[key];
      }
    });
    this.ajaxMap = ajaxMap;
    // 添加未加入到dataSourceMap中的接口
    this.ajaxList.forEach((item) => {
      if (!this.dataSourceMap[item.id]) {
        this.dataSourceMap[item.id] = {
          status: DS_STATUS.INIT,
          load: (...args: any) => {
            // @ts-ignore
            return this.getDataSource(item.id, ...args);
          },
        };
      }
    });
    return this.dataSourceMap;
  }

  generateDataSourceMap() {
    const res: any = {};
    this.ajaxList.forEach((item) => {
      res[item.id] = {
        status: DS_STATUS.INIT,
        load: (...args: any) => {
          // eslint-disable-next-line @typescript-eslint/ban-ts-comment
          // @ts-ignore
          return this.getDataSource(item.id, ...args);
        },
      };
    });
    return res;
  }

  updateDataSourceMap(id: string, data: any, error: any) {
    this.dataSourceMap[id].error = error || undefined;
    this.dataSourceMap[id].data = data;
    this.dataSourceMap[id].status = error ? DS_STATUS.ERROR : DS_STATUS.LOADED;
  }

  /**
   * get all dataSourceItems which marked as isInit === true
   * @private
   * @returns
   * @memberof DataHelper
   */
  getInitDataSourseConfigs() {
    const initConfigs = this.parser(this.ajaxList).filter((item: DataSourceItem) => {
      // according to [spec](https://lowcode-engine.cn/lowcode), isInit should be boolean true to be working
      if (item.isInit === true) {
        this.dataSourceMap[item.id].status = DS_STATUS.LOADING;
        return true;
      }
      return false;
    });
    return initConfigs;
  }

  /**
   * process all dataSourceItems which marked as isInit === true, and get dataSource request results.
   * @public
   * @returns
   * @memberof DataHelper
   */
  getInitData() {
    const initSyncData = this.getInitDataSourseConfigs();
    // 所有 datasource 的 datahandler
    return this.asyncDataHandler(initSyncData).then((res) => {
      const { dataHandler } = this.config;
      return this.handleData(null, dataHandler, res, null);
    });
  }

  getDataSource(id: string, params: any, otherOptions: any, callback: any) {
    const req = this.parser(this.ajaxMap[id]);
    const options = req.options || {};
    let callbackFn = callback;
    let otherOptionsObj = otherOptions;
    if (typeof otherOptions === 'function') {
      callbackFn = otherOptions;
      otherOptionsObj = {};
    }
    const { headers, ...otherProps } = otherOptionsObj || {};
    if (!req) {
      console.warn(`getDataSource API named ${id} not exist`);
      return;
    }

    return this.asyncDataHandler([
      {
        ...req,
        options: {
          ...options,
          // 支持参数为array的情况，当参数为array时，不做参数合并
          params:
            Array.isArray(options.params) || Array.isArray(params)
              ? params || options.params
              : {
                ...options.params,
                ...params,
              },
          headers: {
            ...options.headers,
            ...headers,
          },
          ...otherProps,
        },
      },
    ])
    .then((res: any) => {
      try {
        callbackFn && callbackFn(res && res[id]);
      } catch (e) {
        console.error('load请求回调函数报错', e);
      }
      return res && res[id];
    })
    .catch((err) => {
      try {
        callbackFn && callbackFn(null, err);
      } catch (e) {
        console.error('load请求回调函数报错', e);
      }
      return err;
    });
  }

  asyncDataHandler(asyncDataList: any[]) {
    return new Promise((resolve, reject) => {
      const allReq: any[] = [];
      asyncDataList.forEach((req) => {
        const { id, type } = req;
        // TODO: need refactoring to remove 'legao' related logic
        if (!id || !type || type === 'legao') {
          return;
        }
        allReq.push(req);
      });

      if (allReq.length === 0) {
        resolve({});
      }
      const res: any = {};
      Promise.all(
        allReq.map((item: any) => {
          return new Promise((innerResolve) => {
            const { type, id, dataHandler, options } = item;

            const fetchHandler = (data: any, error: any) => {
              res[id] = this.handleData(id, dataHandler, data, error);
              this.updateDataSourceMap(id, res[id], error);
              innerResolve({});
            };

            const doFetch = (innerType: string, innerOptions: any) => {
              doRequest(innerType as any, innerOptions)
                ?.then((data: any) => {
                  fetchHandler(data, undefined);
                })
                .catch((err: Error) => {
                  fetchHandler(undefined, err);
                });
            };

            this.dataSourceMap[id].status = DS_STATUS.LOADING;
            doFetch(type, options);
          });
        }),
      ).then(() => {
        resolve(res);
      }).catch((e) => {
        reject(e);
      });
    });
  }

  /**
   * process data using dataHandler
   *
   * @param {(string | null)} id request id, will be used in error message, can be null
   * @param {*} dataHandler
   * @param {*} data
   * @param {*} error
   * @returns
   * @memberof DataHelper
   */
  handleData(id: string | null, dataHandler: any, data: any, error: any) {
    let dataHandlerFun = dataHandler;
    if (isJSFunction(dataHandler)) {
      dataHandlerFun = transformStringToFunction(dataHandler.value);
    }
    if (!dataHandlerFun || typeof dataHandlerFun !== 'function') {
      return data;
    }
    try {
      return dataHandlerFun.call(this.host, data, error);
    } catch (e) {
      if (id) {
        console.error(`[${id}]单个请求数据处理函数运行出错`, e);
      } else {
        console.error('请求数据处理函数运行出错', e);
      }
    }
  }
}
