import { IDataSourceQuery, deserializeDynamicMap, EComponentType, normalizeQuery } from '@cloudbase/cals';
import { IReactionDisposer, autorun, observable, toJS } from 'mobx';
import {
  IGenerateContext,
  IGenerateOptions,
  generateDataBinds,
  generateListnerInstances,
  splitDynamicMap,
} from './adapter/generate';
import { generateEmit, mergeDynamic2StaticData } from './runtime';
import { debounce, get } from 'lodash-es';

export interface IQueryContext extends IGenerateContext {
  $w: any;
}

const enum EQUERY_PARAM_TYPE {
  ARRAY = 'ARRAY',
  BOOLEAN = 'BOOLEAN',
  NUMBER = 'NUMBER',
  OBJECT = 'OBJECT',
  STRING = 'STRING',
}

export class Query {
  #schema: IDataSourceQuery;
  #context: IQueryContext;
  #triggered = false;
  #disposes: IReactionDisposer[] = [];
  #dataBinds: Record<string, Function> = {};
  #action: any;
  #paramsRef: { current: any } = observable({ current: null });
  #emit: (eventName, data) => Promise<any>;
  #currentRequestKey = null;
  #observableValue: { data: any; error: Error; isFetching: boolean } = observable({
    data: null,
    error: null,
    isFetching: false,
  });

  constructor({
    schema,
    context,
    options,
  }: {
    schema: IDataSourceQuery;
    context: IQueryContext;
    options?: IGenerateOptions;
  }) {
    const { looseError = false, useScopeWidgetData = false } = options;
    const { $w, $app, $page } = context;
    this.#schema = normalizeQuery(schema);
    this.#context = context;

    if (this.#schema?.trigger === 'auto') {
      this.#disposes.push(
        autorun(
          (r) => {
            try {
              const data = this.#resolveParams();
              if (this.#triggered) {
                this.#debounceTrigger(data);
              }
            } catch (e) {
              console.error(e);
            }
          },
          { delay: 50 },
        ),
      );
    }

    const deserializeContext = $page?.id
      ? {
          page: { id: $page.id, type: EComponentType.PAGE },
        }
      : {};

    const processedParams = deserializeDynamicMap(deserializeContext, this.#schema.handler?.params || {});

    const { staticData, dataBinds = [] } = splitDynamicMap(processedParams);

    this.#paramsRef.current = staticData;
    this.#dataBinds = generateDataBinds(
      { $page, $app },
      dataBinds?.filter((item) => {
        return !/[\s+\-*/!&|%]*?SERVER\./.test(item.propertyPath);
      }),
      {
        looseError,
        useScopeWidgetData,
        ...options,
        key: `${this.id} method.params`,
      },
    );

    this.#action =
      this.#schema.type === 'sql'
        ? async function (data) {
            const Data = await $w.cloud.callWedaApi({
              action: 'RunPluginQuery',
              data: {
                EnvId: $w.env.envId,
                MainAppId: $w.app.id,
                Name: data.sqlTemplateId,
                Parameter: JSON.stringify(
                  Object.entries(data.params || {}).reduce((list, [key, value]) => {
                    if (value !== undefined) {
                      let type = EQUERY_PARAM_TYPE.OBJECT;
                      const typeofValue = typeof value;
                      switch (typeofValue) {
                        case 'boolean': {
                          type = EQUERY_PARAM_TYPE.BOOLEAN;
                          break;
                        }
                        case 'number': {
                          type = EQUERY_PARAM_TYPE.NUMBER;
                          break;
                        }
                        case 'string': {
                          type = EQUERY_PARAM_TYPE.STRING;
                          break;
                        }
                        default: {
                          if (Array.isArray(value)) {
                            type = EQUERY_PARAM_TYPE.ARRAY;
                          } else {
                            type = EQUERY_PARAM_TYPE.OBJECT;
                          }
                        }
                      }

                      list.push({
                        Key: key,
                        Type: type,
                        Value: type === EQUERY_PARAM_TYPE.STRING ? value : JSON.stringify(value),
                      });
                    }

                    return list;
                  }, []) || [],
                ),
              },
            });

            const { ExecuteResultList = [], Total } = Data || {};
            return {
              records: ExecuteResultList.map((item) => JSON.parse(item)),
              total: Total || ExecuteResultList.length,
            };
          }
        : get($w.app, this.#schema.handler?.name) ||
          function (data) {
            throw new Error('Invalue query handler');
          };

    this.#emit = async (trigger, eventData, originalEvent?) =>
      generateEmit($w)(
        trigger,
        generateListnerInstances(
          {
            $app,
            $page,
            actionsMap: (this.#context as any).actionsMap,
          },
          this.#schema.listeners || [],
        ),
        eventData,
        {},
        originalEvent,
        {},
        {},
        /**
         * 当 app 全局情况下
         * 不进行 pageId 判断
         */
        $w.page?.id,
        true,
      );

    return this;
  }

  get id(): string {
    return this.#schema?.id || '';
  }

  get label(): string {
    return this.#schema?.label || '';
  }

  get description(): string {
    return this.#schema?.description || '';
  }

  get data() {
    return this.#observableValue.data;
  }

  get error() {
    return this.#observableValue.error;
  }

  get isFetching() {
    return this.#observableValue.isFetching;
  }

  async trigger(additionalScope?, options = {}) {
    this.#triggered = true;
    return this.#innerTrigger(this.#resolveParams(additionalScope), options);
  }

  reset() {
    this.#observableValue.data = null;
    this.#observableValue.error = null;
    this.#observableValue.isFetching = false;
  }

  destroy() {
    this.#disposes.forEach((dispose) => dispose());
  }

  /**
   * 用于调试
   */
  _toSchema() {
    console.warn('调试使用，结构可能发生更改，请不要用于生产环境');
    return this.#schema;
  }

  async #innerTrigger(data, options = {}) {
    this.#currentRequestKey = Date.now();
    const key = this.#currentRequestKey;
    try {
      this.#observableValue.isFetching = true;
      const res = await this.#action(data);
      if (key === this.#currentRequestKey) {
        this.#observableValue.isFetching = false;
        this.#observableValue.data = res;
        this.#observableValue.error = null;
        this.#emit(`success`, res);
      }
      return res;
    } catch (e) {
      if (key === this.#currentRequestKey) {
        this.#observableValue.isFetching = false;
        this.#observableValue.data = null;
        this.#observableValue.error = e;
        this.#emit(`fail`, e);
      }
      throw e;
    }
  }

  #debounceTrigger = debounce(this.#innerTrigger, 300);

  #resolveParams(additionalScope = {}) {
    /**
     * 这里万一其中某个字段计算失败
     * 好像会阻塞其他字段的计算
     * 从而导致 autorun 没有添加依赖
     */
    return mergeDynamic2StaticData(toJS(this.#paramsRef.current), this.#dataBinds, {
      $w: this.#context.$w,
      paramsContext: additionalScope,
    });
  }

  toJSON() {
    const KEYS = ['id', 'label', 'description', 'data', 'error', 'isFetching', 'trigger', 'reset'];
    return KEYS.reduce((obj, key) => {
      obj[key] = this[key];
      return obj;
    }, {});
  }
}
