import { isEqual } from 'lodash';
import { number } from 'yargs';
import { createObject } from './helper';
import { register as registerBulitin, getFilters } from './tpl-builtin';
import { register as registerLodash } from './tpl-lodash';
import { parse, evaluate } from 'amis-formula';
import { BigNumber } from 'bignumber.js';
import { add } from 'lodash';

export interface Enginer {
  test: (tpl: string) => boolean;
  removeEscapeToken?: (tpl: string) => string;
  compile: (tpl: string, data: object, ...rest: Array<any>) => string;
}

const enginers: {
  [propName: string]: Enginer;
} = {};

export function registerTplEnginer(name: string, enginer: Enginer) {
  enginers[name] = enginer;
}
export const tplReg = /\$\{.+?\}|<%.+?%>/ // 模板字符的匹配表达式 匹配 "ddddd ${aa} ddd"的字符

const tplItemsReg = /\$\{items\|.+?\}/ // 模板字符的匹配表达式 匹配 "ddddd ${items|aa} ddd"的字符

// 暂存的数据缓存 缓存计算过的数据结果 防止重复计算
let curDataCahe = {}
let curDataCaheTimer: any // 定时器标识
export function filter(
  tpl?: any,
  data: any = {},
  ...rest: Array<any>
): string {
  // 记录代码块开始时间
  if (!tpl || typeof tpl !== 'string') {
    return '';
  }


  // 如果不是模板字符无需进行模板替换
  if (!tplReg.test(tpl)) return tpl
  const keys = Object.keys(enginers);
  const sum = {}

  let sumData


  // 需要用到items的并且有才需要跌迭代压缩一下items 否则没必要
  if (tplItemsReg.test(tpl) && data.items) {
    if (data.dataSetId && curDataCahe[data.dataSetId]) {
      sumData = { items: [curDataCahe[data.dataSetId]] }
    } else {
      for (const item of data?.items) {
        for (const key in item) {
          if (typeof item[key] === 'object') continue
          if (typeof item[key] === 'number' || !Number.isNaN(+item[key])) {
            sum[key] = +sum[key] || 0
            sum[key] = new BigNumber(sum[key]).plus(+item[key] || 0)
          } else if (typeof item[key] === 'string') {
            sum[key] += item[key]
          }
        }
      }
      curDataCahe[data.dataSetId] = sum
      sumData = { items: [sum] }

      // 同一时间段重复的数据只计算一次 
      if (curDataCaheTimer)
        clearTimeout(curDataCaheTimer)
      curDataCaheTimer = setTimeout(() => curDataCahe = {}, 500)
    }
  } else {
    sumData = data
  }
  for (let i = 0, len = keys.length; i < len; i++) {
    let enginer = enginers[keys[i]];
    if (enginer.test(tpl)) {
      return enginer.compile(tpl, sumData, ...rest);
    } else if (enginer.removeEscapeToken) {
      tpl = enginer.removeEscapeToken(tpl);
    }
  }

  return tpl;
}

// 缓存一下提升性能
const EVAL_CACHE: { [key: string]: Function } = {};

let customEvalExpressionFn: (expression: string, data?: any) => boolean;
export function setCustomEvalExpression(
  fn: (expression: string, data?: any) => boolean
) {
  customEvalExpressionFn = fn;
}

// 几乎所有的 visibleOn requiredOn 都是通过这个方法判断出来结果，很粗暴也存在风险，建议自己实现。
// 如果想自己实现，请通过 setCustomEvalExpression 来替换。
export function evalExpression(expression: string, data?: object): boolean {

  if (typeof customEvalExpressionFn === 'function') {
    return customEvalExpressionFn(expression, data);
  }

  if (!expression || typeof expression !== 'string') {
    return false;
  }

  /* jshint evil:true */
  try {
    if (
      typeof expression === 'string' &&
      expression.substring(0, 2) === '${' &&
      expression[expression.length - 1] === '}'
    ) {
      // 启用新版本的公式表达式
      return evalFormula(expression, data);
    }
    let debug = false;
    const idx = expression.indexOf('debugger');
    if (~idx) {
      debug = true;
      expression = expression.replace(/debugger;?/, '');
    }

    let fn;
    if (expression in EVAL_CACHE) {
      fn = EVAL_CACHE[expression];
    } else {
      fn = new Function(
        'data',
        'utils',
        `with(data) {${debug ? 'debugger;' : ''}return !!(${expression});}`
      );
      EVAL_CACHE[expression] = fn;
    }

    data = data || {};
    return fn.call(data, data, getFilters());
  } catch (e) {
    console.warn(expression, e);
    return false;
  }
}

const AST_CACHE: { [key: string]: any } = {};
function evalFormula(expression: string, data: any) {
  const ast =
    AST_CACHE[expression] ||
    parse(expression, {
      evalMode: false
    });
  AST_CACHE[expression] = ast;

  return evaluate(ast, data, {
    defaultFilter: 'raw'
  });
}

let customEvalJsFn: (expression: string, data?: any) => any;
export function setCustomEvalJs(fn: (expression: string, data?: any) => any) {
  customEvalJsFn = fn;
}

// 这个主要用在 formula 里面，用来动态的改变某个值。也很粗暴，建议自己实现。
// 如果想自己实现，请通过 setCustomEvalJs 来替换。
export function evalJS(js: string, data: object): any {
  if (typeof customEvalJsFn === 'function') {
    return customEvalJsFn(js, data);
  }

  /* jshint evil:true */
  try {
    if (
      typeof js === 'string' &&
      js.substring(0, 2) === '${' &&
      js[js.length - 1] === '}'
    ) {
      // 启用新版本的公式表达式
      return evalFormula(js, data);
    }

    const fn = new Function(
      'data',
      'utils',
      `with(data) {${/^\s*return\b/.test(js) ? '' : 'return '}${js};}`
    );
    data = data || {};
    return fn.call(data, data, getFilters());
  } catch (e) {
    console.warn(js, e);
    return null;
  }
}

[registerBulitin, registerLodash].forEach(fn => {
  const info = fn();

  registerTplEnginer(info.name, {
    test: info.test,
    compile: info.compile,
    removeEscapeToken: info.removeEscapeToken
  });
});
