import { evaluate } from "amis-formula";
import { isString } from "lodash";
import isObjectByLodash from 'lodash/isObject';
import { collectVariables } from "./grammar";
import { getVariable } from "./helper";
import { filter } from "./tpl";
import { getFilters, resolveVariableAndFilter } from "./tpl-builtin";

/**
 * formulaExec 运算器：根据当前字符串类型执行对应运算，也可按指定执行模式执行运算
 *
 * 运算模式（execMode）支持以下取值:
 * 1. tpl: 按模板字符串执行（JavaScript 模板引擎），比如：Hello ${amisUser.email}、<h1>Hello</h1>, <span>${amisUser.email}</span>；
 *    备注: 在模板中可以自由访问变量，详细请见：https://www.lodashjs.com/docs/lodash.template；
 * 2. formula: 按新版公式表达式执行，用于执行 ${ xxx } 格式的表达式；
 *    支持从window、localStorage、sessionStorage获取数据，比如：${num1 + 2}、${ls:env}、${window:document}、${window:document.URL}、${amisUser.email}；
 *    详细请见：https://aisuda.bce.baidu.com/amis/zh-CN/docs/concepts/data-mapping#namespace
 * 3. evalFormula: 按新版公式表达式执行，用于执行 ${ xxx } 和 非${ xxx } 格式的表达式（evalMode 为 true，不用 ${} 包裹也可以执行），功能同 formula 运算模式；
 * 4. js: 按Javascript执行，表达式中可以通过data.xxx来获取指定数据，并且支持简单运算；
 *    比如：data.num1 + 2、this.num1 + 2、num1 + 2；（备注：三个表达式是等价的，这里的 this 就是 data。）
 * 5. var: 以此字符串作为key值从当前数据域data中获取数值；性能最高（运行期间不会生成ast和表达式运算）；
 * 6. true 或者 false: 当execMode设置为true时，不用 ${} 包裹也可以执行表达式；
 * 7. collect: 用于从表达式中获取所有变量；
 *
 * 备注1: 用户也可以使用 registerFormulaExec 注册一个自定义运算器；
 * 备注2: 模板字符串 和 Javascript 模板引擎 不可以交叉使用；
 * 备注3: amis 现有的 evalFormula 方法，可执行 ${} 格式类表达式，但不支持 filter 过滤器，所以这里用 resolveValueByName 实现；
 * 备注4: 后续可考虑将 amis现有的运算器都放这里管理，充当统一的运算器入口。
 */

// 缓存，用于提升性能
const FORMULA_EVAL_CACHE: { [key: string]: Function } = {};

/**
 * 用于存储当前可用运算器，默认支持 tpl、formula、js、var 四种类型运算器
 * 备注：在这里统一参数。
 */
export const FormulaExec: {
  [key: string]: Function;
} = {
  tpl: (expression: string, data?: object) => {
    const curData = data || {};
    return filter(expression, curData);
  },
  formula: (expression: string, data?: object) => {
    // 邮箱格式直接返回，后续需要在 amis-formula 中处理
    if (
      /^\$\{([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})\}$/.test(
        expression
      )
    ) {
      return expression.substring(2, expression.length - 1); // 剔除前后特殊字符
    }
    const curData = data || {};
    let result = undefined;
    try {
      // 执行 ${} 格式类表达式，且支持 filter 过滤器。（备注: isPureVariable 可用于判断是否有 过滤器。）
      result = resolveVariableAndFilter(expression, curData, '| raw');
    } catch (e) {
      console.warn(
        '[formula]表达式执行异常，当前表达式: ',
        expression,
        '，当前上下文数据: ',
        data
      );
      return expression;
    }
    // 备注: 此处不用 result ?? expression 是为了避免 没有对应结果时直接显示 expression: ${xxx}
    return result;
  },
  evalFormula: (expression: string, data?: object) => {
    const curData = data || {};
    let result = undefined;
    try {
      result = evaluate(expression, curData, {
        evalMode: true, // evalMode 为 true 时，不用 ${} 包裹也可以执行，
        allowFilter: false
      });
    } catch (e) {
      console.warn(
        '[evalFormula]表达式执行异常，当前表达式: ',
        expression,
        '，当前上下文数据: ',
        data
      );
      return expression;
    }
    return result ?? expression;
  },
  js: (expression: string, data?: object) => {
    let debug = false;
    const idx = expression.indexOf('debugger');
    if (~idx) {
      debug = true;
      expression = expression.replace(/debugger;?/, '');
    }

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

    data = data || {};

    let curResult = undefined;
    try {
      curResult = fn.call(data, data, getFilters());
    } catch (e) {
      console.warn(
        '[formula:js]表达式执行异常，当前表达式: ',
        expression,
        '，当前上下文数据: ',
        data
      );
      return expression;
    }
    return curResult;
  },
  var: (expression: string, data?: object) => {
    const curData = data || {};
    const result = getVariable(curData, expression); // 不支持过滤器
    return result ?? expression;
  },
  collect: (expression: any) => {
    let variables: Array<string> = [];
    if (isObjectByLodash(expression) || isString(expression)) {
      // 仅对 Object类型 和 String类型 进行变量提取
      variables = collectVariables(expression);
    } else {
      variables = [];
    }
    return variables;
  }
};

// 用于判断是否优先使用value。
export function isExpression(expression: any): boolean {
  if (!expression || !isString(expression)) {
    // 非字符串类型，比如：Object、Array类型、boolean、number类型
    return false;
  }
  // 备注1: "\\${xxx}"不作为表达式，至少含一个${xxx}才算是表达式

  // 备注2: safari 不支持 /(?<!\\)(\${).+(\})/.test(expression)
  return /(^|[^\\])\$\{.+\}/.test(expression);
}