import isPlainObject from 'lodash/isPlainObject';
import isEqual from 'lodash/isEqual';
import isNaN from 'lodash/isNaN';
import uniq from 'lodash/uniq';
import last from 'lodash/last';
import { Schema, PlainObject, FunctionPropertyNames } from '../types';
import { evalExpression } from './tpl';
import qs from 'qs';
import { IIRendererStore } from '../store';
import { IFormStore } from '../store/form';
import { autobindMethod } from './autobind';
import { isObservable } from 'mobx';
import {
  isPureVariable,
  resolveVariable,
  resolveVariableAndFilter
} from './tpl-builtin';
import MSG from '../renderers/Lion/utils/msgsub';
import { RendererEnv } from '../env';
import { isEqualWith, round } from 'lodash';
import { dealBigMoney, dealBigNumber } from './utils';
import BigNumber from 'bignumber.js';


import { isUndefined } from "lodash";

export const domUtils = {
  /**
   * 一个元素是否匹配一个css选择器
   * @param {Element} dom
   * @param {string} selector
   * @return {boolean}
   */
  matches(dom: Element, selector: string) {
    if (dom instanceof Element && typeof selector === 'string' && selector) {
      if (dom.webkitMatchesSelector) {
        // 兼容android 4.4
        return dom.webkitMatchesSelector(selector);
        // }else if('matchesSelector' in dom){
        // 兼容老版本浏览器
        // return dom.matches(selector);
      } else if (dom.matches) {
        return dom.matches(selector);
      }
    }
    return false;
  },
  /**
   * 获取父级可滚动的节点
   * @param node
   * @returns
   */
  getScrollParent(node: Element): Element | null {
    if (node == null) {
      return null;
    }
    // 兼容壳无法正确计算scrollHeight
    if (node.scrollHeight > node.clientHeight && getComputedStyle(node).overflowY === 'scroll') {
      return node;
    } else {
      return domUtils.getScrollParent(node.parentNode as HTMLElement);
    }
  },
  /**
   * 向上冒泡遍历查找与能与css选择器匹配的元素(包含自身),
   * @param {HTMLElement} target
   * @param {string} selector
   * @param {HTMLElement} stopNode
   * @return {HTMLElement}
   */
  closest: function (target: HTMLElement | SVGElement, selector: string, stopNode?: HTMLElement | SVGElement): HTMLElement | SVGElement | null {

    if ((target instanceof HTMLElement || target instanceof SVGElement)
      && typeof selector === 'string'
      && (isUndefined(stopNode) || stopNode instanceof HTMLElement || stopNode instanceof SVGElement)) {
      let tar: HTMLElement | SVGElement | null = target;
      while (tar) {
        if (domUtils.matches(tar, selector)) {
          return tar;
        }
        tar = tar.parentElement;
        if (stopNode && stopNode.isSameNode(tar)) {
          return null;
        }
      }
    }
    return null;
  }
}

export const isNil = (value: any) => value === null || value === undefined

export function preventDefault(event: TouchEvent | Event): void {
  if (typeof event.cancelable !== 'boolean' || event.cancelable) {
    event.preventDefault();
  }
}

export const create2DArrayByMaxSum = (arr: number[], maxSum: number) => {
  let result = []; // 存储二维数组
  let currentRow: number[] = []; // 存储当前行的元素

  for (let num of arr) {
    // 如果当前行的和加上当前数字不超过最大和，则添加到当前行
    if (currentRow.reduce((sum, val) => sum + val, 0) + num <= maxSum) {
      currentRow.push(num);
    } else {
      // 否则，将当前行添加到结果数组中，并开始新的一行
      result.push(currentRow);
      currentRow = [num]; // 重置当前行为新的数字
    }
  }

  // 不要忘记在最后添加当前行（如果它不是空的）
  if (currentRow.length > 0) {
    result.push(currentRow);
  }

  return result;
}


// 仅进行一帧的方法 类似settimeout(,0)只是这个视图上更流畅
export const functionInOneFrame = (Fn: (...rest: any) => void) => {
  const requestTimerId = requestAnimationFrame(() => {
    Fn()
    cancelAnimationFrame(requestTimerId)
  })
}
/**
  * 标准化展示文本 包含 前后缀 千分位 大写,金额大写 目前只识别到这三个
  */
export function standardValueText(value: string, option: { prefix?: string, suffix?: string, kilobitSeparator?: boolean, precision?: number, showUppercase?: 0 | 1 | 2, carry?: boolean }) {
  if (isNil(value)) return ''
  const {
    prefix,
    suffix,
    precision,
    kilobitSeparator,
    showUppercase,
    carry } = option
  // 小数保留
  if (!Number.isNaN(+value)) {
    if (!isNil(precision) && typeof precision === 'number') {
      if (carry) {
        const val = ("" + value).toString().split('.')
        value = val[0] + (val[1] ? ('.' + val[1]?.slice(0, precision)) : '')
      } else
        value = (+value).toFixed(precision)
    }
    if (kilobitSeparator && !showUppercase) {
      value = numberFormatter(value, precision);
    }
    if (showUppercase === 1) {
      value = dealBigNumber(+value)
    } else if (showUppercase === 2) {
      value = dealBigMoney(+value)
    }
  }
  return (prefix ? prefix : '') + value + (suffix ? suffix : '')

}

window['_standardValueText'] = standardValueText

// 计算一下宿主的js堆允许缓存大小
export const jsMemoryHeap = (performance?.memory?.jsHeapSizeLimit / (1024 * 1024 * 1024)) || 1
// 获取最大的index大1的index值
export const getLagerThanMaxZindex = () => 1 + (Math.max(...[...Array.from(document.querySelectorAll('*')).map(_ => Number(getComputedStyle(_).getPropertyValue('z-index')))].filter(_ => (!isNaN(_) && _ < 1000000000))) as any);

let isMobileResult = /Android|ArkWeb|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
// 计算过一次就不用再计算了
export function isMobile() {
  return isMobileResult
}

// 获取文本中中文个数
export const getChineseWordLengthInWord = (value: string = '') => {
  return (value + '').match(/[^\u0000-\u00ff]/g)?.length || 0
}

// 获取字体的最大字节数-方便通用
export const getValueBitLength = (value: string) => {
  return value.replace(/[^\u0000-\u00ff]/g, 'aa').length
}

export function range(num: number, min: number, max: number): number {
  return Math.min(Math.max(num, min), max);
}


// 黑名单属性-禁止相关属性进行继承 分别是高级查询和基础查询的属性
const blackList = ['advancedFilterSub', 'advancedHeader', 'advancedFilter', 'filterOptionData', 'filterParam', 'perPage', 'page']

// 展开__super
export const flatSuperData = (data: any) => {
  let curData = data
  let flatObj = {}

  // 当当前data有__super的时候 展开
  while (curData.__super) {
    flatObj = { ...curData, ...flatObj }
    curData = curData.__super
  }
  return flatObj
}

// 方便取值的时候能够把上层的取到，但是获取的时候不会全部把所有的数据获取到。
export function createObject(
  superProps?: { [propName: string]: any },
  props?: { [propName: string]: any },
  properties?: any,
  copyBlackProPerty?: boolean
): object {
  const hasBlackListKey = Object.keys(superProps || {}).find(_ => blackList.includes(_))

  if (superProps && (Object.isFrozen(superProps) || hasBlackListKey)) {
    superProps = cloneObject(superProps);
  }
  // 初始化properties
  const tempSearchQuery = {}
  // 如果有黑名单数据 移除掉其中的黑名单数据-防止被继承
  if (hasBlackListKey)
    for (const key of blackList) {
      if (superProps?.hasOwnProperty(key)) {
        //如果需要复制黑名单中的数据
        if (copyBlackProPerty) {
          // 复制黑名单数据
          tempSearchQuery[key] = superProps[key]
        }
        delete superProps[key]
      }
    }
  const obj = superProps
    ? Object.create(superProps, {
      ...properties,
      __super: {
        value: superProps,
        writable: false,
        enumerable: false
      }
    })
    : Object.create(Object.prototype, properties);

  // 如果需要复制，直接加到同层数据中数据中
  if (hasBlackListKey) {
    Object.assign(obj, tempSearchQuery)
  }

  props &&
    isObject(props) &&
    Object.keys(props).forEach(key => (obj[key] = props[key]));

  return obj;
}

export function cloneObject(target: any, persistOwnProps: boolean = true) {
  const obj =
    target && target.__super
      ? Object.create(target.__super, {
        __super: {
          value: target.__super,
          writable: false,
          enumerable: false
        }
      })
      : Object.create(Object.prototype);
  persistOwnProps &&
    target &&
    Object.keys(target).forEach(key => (obj[key] = target[key]));
  return obj;
}

/**
 * 给目标对象添加其他属性，可读取但是不会被遍历。
 * @param target
 * @param props
 */
export function injectPropsToObject(target: any, props: any) {
  const sup = Object.create(target.__super || null);
  Object.keys(props).forEach(key => (sup[key] = props[key]));
  const result = Object.create(sup);
  Object.keys(target).forEach(key => (result[key] = target[key]));
  return result;
}

export function extendObject(
  target: any,
  src?: any,
  persistOwnProps: boolean = true
) {
  const obj = cloneObject(target, persistOwnProps);
  src && Object.keys(src).forEach(key => (obj[key] = src[key]));
  return obj;
}

export function isSuperDataModified(
  data: any,
  prevData: any,
  store: IIRendererStore
) {
  let keys: Array<string> = [];

  if (store && store.storeType === 'FormStore') {
    keys = uniq(
      (store as IFormStore).items
        .map(item => `${item.name}`.replace(/\..*$/, ''))
        .concat(Object.keys(store.data))
    );
  } else {
    keys = Object.keys(store.data);
  }

  if (Array.isArray(keys) && keys.length) {
    return keys.some(key => data[key] !== prevData[key]);
  }

  return false;
}

export function syncDataFromSuper(
  data: any,
  superObject: any,
  prevSuperObject: any,
  store: IIRendererStore,
  force: boolean
) {
  const obj = {
    ...data
  };

  let keys: Array<string> = [];

  // 如果是 form store，则从父级同步 formItem 种东西。
  if (store && store.storeType === 'FormStore') {
    keys = uniq(
      (store as IFormStore).items
        .map(item => `${item.name}`.replace(/\..*$/, ''))
        .concat(Object.keys(obj))
    );
  } else if (force) {
    keys = Object.keys(obj);
  }

  if (superObject || prevSuperObject) {
    keys.forEach(key => {
      if (!key) {
        return;
      }

      if (
        ((superObject && typeof superObject[key] !== 'undefined') ||
          (prevSuperObject && typeof prevSuperObject[key] !== 'undefined')) &&
        ((prevSuperObject && !superObject) ||
          (!prevSuperObject && superObject) ||
          prevSuperObject[key] !== superObject[key])
      ) {
        obj[key] = superObject[key];
      }
    });
  }

  return obj;
}

/**
 * 生成 8 位随机数字。
 *
 * @return {string} 8位随机数字
 */
export function guid() {
  function s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
      .toString(16)
      .substring(1);
  }
  return s4() + s4() + s4();
}

export function findIndex(
  arr: Array<any>,
  detect: (item?: any, index?: number) => boolean
) {
  for (let i = 0, len = arr.length; i < len; i++) {
    if (detect(arr[i], i)) {
      return i;
    }
  }

  return -1;
}


export function getVariable(
  data: { [propName: string]: any },
  key: string | undefined,
  canAccessSuper: boolean = true
): any {
  if (!data || !key) {
    return undefined;
  } else if (canAccessSuper ? key in data : data.hasOwnProperty(key)) {
    return data[key];
  }

  return keyToPath(key).reduce(
    (obj, key) =>
      obj &&
        typeof obj === 'object' &&
        (canAccessSuper ? key in obj : obj.hasOwnProperty(key))
        ? obj[key]
        : undefined,
    data
  );
}

export function setVariable(
  data: { [propName: string]: any },
  key: string,
  value: any,
  convertKeyToPath?: boolean
) {
  data = data || {};

  if (key in data) {
    data[key] = value;
    return;
  }

  const parts = convertKeyToPath !== false ? keyToPath(key) : [key];
  const last = parts.pop() as string;

  while (parts.length) {
    let key = parts.shift() as string;
    if (isPlainObject(data[key])) {
      data = data[key] = {
        ...data[key]
      };
    } else if (Array.isArray(data[key])) {
      data[key] = data[key].concat();
      data = data[key];
    } else if (data[key]) {
      // throw new Error(`目标路径不是纯对象，不能覆盖`);
      // 强行转成对象
      data[key] = {};
      data = data[key];
    } else {
      data[key] = {};
      data = data[key];
    }
  }

  data[last] = value;
}


export function deleteVariable(data: { [propName: string]: any }, key: string) {
  if (!data) {
    return;
  } else if (data.hasOwnProperty(key)) {
    delete data[key];
    return;
  }

  const parts = keyToPath(key);
  const last = parts.pop() as string;

  while (parts.length) {
    let key = parts.shift() as string;
    if (isPlainObject(data[key])) {
      data = data[key] = {
        ...data[key]
      };
    } else if (data[key]) {
      throw new Error(`目标路径不是纯对象，不能修改`);
    } else {
      break;
    }
  }

  if (data && data.hasOwnProperty && data.hasOwnProperty(last)) {
    delete data[last];
  }
}


export function hasOwnProperty(
  data: { [propName: string]: any },
  key: string
): boolean {
  const parts = keyToPath(key);

  while (parts.length) {
    let key = parts.shift() as string;
    if (!isObject(data) || !data.hasOwnProperty(key)) {
      return false;
    }

    data = data[key];
  }

  return true;
}

export function noop() { }

export function anyChanged(
  attrs: string | Array<string>,
  from: { [propName: string]: any },
  to: { [propName: string]: any },
  strictMode: boolean = true
): boolean {
  return (typeof attrs === 'string' ? attrs.split(/\s*,\s*/) : attrs).some(
    key => (strictMode ? from?.[key] !== to?.[key] : from?.[key] != to?.[key])
  );
}

export function getChangedProp(
  attrs: string | Array<string>,
  from: { [propName: string]: any },
  to: { [propName: string]: any },
  strictMode: boolean = true
): { key: string, fromValue: any, toValue: any }[] {
  return (typeof attrs === 'string' ? attrs.split(/\s*,\s*/) : attrs).filter(
    key => (strictMode ? from?.[key] !== to?.[key] : from?.[key] != to?.[key])
  ).map(key => ({
    key,
    fromValue: from?.[key],
    toValue: to?.[key],
  }));
}

export function rmUndefined(obj: PlainObject) {
  const newObj: PlainObject = {};

  if (typeof obj !== 'object') {
    return obj;
  }

  const keys = Object.keys(obj);
  keys.forEach(key => {
    if (obj[key] !== undefined) {
      newObj[key] = obj[key];
    }
  });

  return newObj;
}


// 原来的，在非嚴格模式下可以使用,因為isequal會嚴格比較,留下做个对照
export function oldIsObjectShallowModified(
  prev: any,
  next: any,
  strictMode: boolean = true,
  ignoreUndefined: boolean = false,
): boolean {
  if (Array.isArray(prev) && Array.isArray(next)) {
    return prev.length !== next.length
      ? true
      : prev.some((prev, index) => {
        oldIsObjectShallowModified(
          prev,
          next[index],
          strictMode,
          ignoreUndefined,
        )
      }
      );
  } else if (Number.isNaN(prev) && Number.isNaN(next)) {
    return false;
  } else if (
    null == prev ||
    null == next ||
    !isObject(prev) ||
    !isObject(next) ||
    isObservable(prev) ||
    isObservable(next)
  ) {
    return strictMode ? prev !== next : prev != next;
  }

  if (ignoreUndefined) {
    prev = rmUndefined(prev);
    next = rmUndefined(next);
  }

  const keys = Object.keys(prev);
  const nextKeys = Object.keys(next);
  if (
    keys.length !== nextKeys.length ||
    keys.sort().join(',') !== nextKeys.sort().join(',')
  ) {
    return true;
  }


  for (let i: number = keys.length - 1; i >= 0; i--) {
    let key = keys[i];
    // 跳过对元数据的比较 也没啥用比较了
    if (['item', 'itemsRaw', 'sortItemsRaw', 'unSelectedItems', 'selectedItems'].includes(key)) continue
    if (
      oldIsObjectShallowModified(
        prev[key],
        next[key],
        strictMode,
        ignoreUndefined,
      )
    ) {
      return true;
    }
  }
  return false;
}


export function isObjectShallowModified(
  prev: any,
  next: any,
  strictMode: boolean = true,
  ignoreUndefined: boolean = false,
): boolean {
  if (strictMode)
    return !isEqual(prev, next)
  return !isEqualWith(prev, next, (objValue, otherValue) => {
    if (isObject(objValue) && isObject(otherValue)) {
      return undefined;
    }
    return objValue == otherValue
  })
}

export function isArrayChildrenModified(
  prev: Array<any>,
  next: Array<any>,
  strictMode: boolean = true
) {
  if (!Array.isArray(prev) || !Array.isArray(next)) {
    return strictMode ? prev !== next : prev != next;
  }

  if (prev.length !== next.length) {
    return true;
  }

  for (let i: number = prev.length - 1; i >= 0; i--) {
    if (strictMode ? prev[i] !== next[i] : prev[i] != next[i]) {
      return true;
    }
  }

  return false;
}

export function immutableExtends(to: any, from: any, deep = false) {
  // 不是对象，不可以merge
  if (!isObject(to) || !isObject(from)) {
    return to;
  }

  let ret = to;

  Object.keys(from).forEach(key => {
    const origin = to[key];
    const value = from[key];

    // todo 支持深度merge
    if (origin !== value) {
      // 一旦有修改，就创建个新对象。
      ret = ret !== to ? ret : { ...to };
      ret[key] = value;
    }
  });

  return ret;
}

// 即将抛弃
export function makeColumnClassBuild(
  steps: number,
  classNameTpl: string = 'col-sm-$value'
) {
  let count = 12;
  let step = Math.floor(count / steps);

  return function (schema: Schema) {
    if (
      schema.columnClassName &&
      /\bcol-(?:xs|sm|md|lg)-(\d+)\b/.test(schema.columnClassName)
    ) {
      const flex = parseInt(RegExp.$1, 10);
      count -= flex;
      steps--;
      step = Math.floor(count / steps);
      return schema.columnClassName;
    } else if (schema.columnClassName) {
      count -= step;
      steps--;
      return schema.columnClassName;
    }

    count -= step;
    steps--;
    return classNameTpl.replace('$value', '' + step);
  };
}

export function hasVisibleExpression(schema: {
  visibleOn?: string;
  hiddenOn?: string;
  visible?: boolean;
  hidden?: boolean;
}) {
  return schema?.visibleOn || schema?.hiddenOn;
}

export function isVisible(
  schema: {
    visibleOn?: string;
    hiddenOn?: string;
    visible?: boolean;
    hidden?: boolean;
  },
  data?: object
) {
  return !(
    schema?.hidden ||
    schema?.visible === false ||
    (schema?.hiddenOn && evalExpression(schema.hiddenOn, data) === true) ||
    (schema?.visibleOn && evalExpression(schema.visibleOn, data) === false)
  );
}

export function isUnfolded(
  node: any,
  config: {
    foldedField?: string;
    unfoldedField?: string;
  }
): boolean {
  let { foldedField, unfoldedField } = config;

  unfoldedField = unfoldedField || 'unfolded';
  foldedField = foldedField || 'folded';

  let ret: boolean = false;
  if (unfoldedField && typeof node[unfoldedField] !== 'undefined') {
    ret = !!node[unfoldedField];
  } else if (foldedField && typeof node[foldedField] !== 'undefined') {
    ret = !node[foldedField];
  }

  return ret;
}

/**
 * 过滤掉被隐藏的数组元素
 */
export function visibilityFilter(items: any, data?: object) {
  return items.filter((item: any) => {
    return isVisible(item, data);
  });
}

export function isDisabled(
  schema: {
    disabledOn?: string;
    disabled?: boolean;
  },
  data?: object
) {
  return (
    schema.disabled ||
    (schema.disabledOn && evalExpression(schema.disabledOn, data))
  );
}

export function hasAbility(
  schema: any,
  ability: string,
  data?: object,
  defaultValue: boolean = true
): boolean {
  return schema.hasOwnProperty(ability)
    ? schema[ability]
    : schema.hasOwnProperty(`${ability}On`)
      ? evalExpression(schema[`${ability}On`], data || schema)
      : defaultValue;
}

export function makeHorizontalDeeper(
  horizontal: {
    left: string;
    right: string;
    offset: string;
    leftFixed?: any;
  },
  count: number
): {
  left: string | number;
  right: string | number;
  offset: string | number;
  leftFixed?: any;
} {
  if (count > 1 && /\bcol-(xs|sm|md|lg)-(\d+)\b/.test(horizontal.left)) {
    const flex = parseInt(RegExp.$2, 10) * count;
    return {
      leftFixed: horizontal.leftFixed,
      left: flex,
      right: 12 - flex,
      offset: flex
    };
  } else if (count > 1 && typeof horizontal.left === 'number') {
    const flex = horizontal.left * count;

    return {
      leftFixed: horizontal.leftFixed,
      left: flex,
      right: 12 - flex,
      offset: flex
    };
  }

  return horizontal;
}

export function promisify<T extends Function>(
  fn: T
): (...args: Array<any>) => Promise<any> & {
  raw: T;
} {
  let promisified = function () {
    try {
      const ret = fn.apply(null, arguments);
      if (ret && ret.then) {
        return ret;
      } else if (typeof ret === 'function') {
        // thunk support
        return new Promise((resolve, reject) =>
          ret((error: boolean, value: any) =>
            error ? reject(error) : resolve(value)
          )
        );
      }
      return Promise.resolve(ret);
    } catch (e) {
      return Promise.reject(e);
    }
  };
  (promisified as any).raw = fn;
  return promisified;
}

export function getScrollParent(node: HTMLElement): HTMLElement | null {
  if (node == null) {
    return null;
  }

  const style = getComputedStyle(node);

  if (!style) {
    return null;
  }

  const text =
    style.getPropertyValue('overflow') +
    style.getPropertyValue('overflow-x') +
    style.getPropertyValue('overflow-y');

  if (/auto|scroll/.test(text) || node.nodeName === 'BODY') {
    return node;
  }

  return getScrollParent(node.parentNode as HTMLElement);
}

// 获取文字再元素中的宽度
export const getTextWidth = (text: string, element?: HTMLElement) =>// 获取文本的长度（像素）的函数
{
  // 创建一个临时的span元素来计算文本的长度
  const span = document.createElement('div');
  span.style.fontSize = '13px';
  span.style.visibility = 'hidden';
  span.style.display = 'inline';
  span.style.whiteSpace = 'nowrap'; /* 禁止换行 */
  span.innerHTML = text;

  if (element) {
    // 获取计算后的样式
    const computedStyle = window.getComputedStyle(element);

    // 设置span元素的样式以匹配目标元素
    span.style.fontFamily = computedStyle.fontFamily;
    span.style.fontSize = computedStyle.fontSize;
    span.style.fontWeight = computedStyle.fontWeight;
    span.style.fontStyle = computedStyle.fontStyle;
    span.style.letterSpacing = computedStyle.letterSpacing;
  }
  // 将span元素添加到文档中
  document.body.appendChild(span);

  // 获取文本的宽度
  const width = span.offsetWidth;

  // 移除临时创建的span元素
  document.body.removeChild(span);

  // 匀出半个字的像素冗余-对应表格自身的padding+border-防止出现超长字段
  return Math.min(width + 2, 400);
}

export const focusInputAndChooseInput = (inputDom: HTMLInputElement) => {
  const len = inputDom?.value?.length + 1;
  if (!inputDom) return
  inputDom.focus()
  // inputDom.scrollIntoView({ behavior: 'smooth', block: 'center' })
  functionInOneFrame(() => {
    inputDom.setSelectionRange(0, len + 1)
  })
}

/**
 * Deep diff between two object, using lodash
 * @param  {Object} object Object compared
 * @param  {Object} base   Object to compare with
 * @return {Object}        Return a new object who represent the diff
 */
export function difference<
  T extends { [propName: string]: any },
  U extends { [propName: string]: any }
>(object: T, base: U, keepProps?: Array<string>): { [propName: string]: any } {
  function changes(object: T, base: U) {
    if (isObject(object) && isObject(base)) {
      const keys: Array<keyof T & keyof U> = uniq(
        Object.keys(object).concat(Object.keys(base))
      );
      let result: any = {};

      keys.forEach(key => {
        const a: any = object[key as keyof T];
        const b: any = base[key as keyof U];

        if (keepProps && ~keepProps.indexOf(key as string)) {
          result[key] = a;
        }

        if (isEqual(a, b)) {
          return;
        }

        if (!object.hasOwnProperty(key)) {
          result[key] = undefined;
        } else if (Array.isArray(a) && Array.isArray(b)) {
          result[key] = a;
        } else {
          result[key] = changes(a as any, b as any);
        }
      });

      return result;
    } else {
      return object;
    }
  }
  return changes(object, base);
}

export const padArr = (arr: Array<any>, size = 4): Array<Array<any>> => {
  const ret: Array<Array<any>> = [];
  const pool: Array<any> = arr.concat();
  let from = 0;

  while (pool.length) {
    let host: Array<any> = ret[from] || (ret[from] = []);

    if (host.length >= size) {
      from += 1;
      continue;
    }

    host.push(pool.shift());
  }

  return ret;
};

export function __uri(id: string) {
  return id;
}


export function isObject(obj: any) {
  const typename = typeof obj;
  return (
    obj &&
    typename !== 'string' &&
    typename !== 'number' &&
    typename !== 'boolean' &&
    typename !== 'function' &&
    !Array.isArray(obj)
  );
}

// xs < 768px
// sm >= 768px
// md >= 992px
// lg >= 1200px
export function isBreakpoint(str: string): boolean {
  if (typeof str !== 'string') {
    return !!str;
  }

  const breaks = str.split(/\s*,\s*|\s+/);

  if ((window as any).matchMedia) {
    return breaks.some(
      item =>
        item === '*' ||
        (item === 'xs' &&
          matchMedia(`screen and (max-width: 767px)`).matches) ||
        (item === 'sm' &&
          matchMedia(`screen and (min-width: 768px) and (max-width: 991px)`)
            .matches) ||
        (item === 'md' &&
          matchMedia(`screen and (min-width: 992px) and (max-width: 1199px)`)
            .matches) ||
        (item === 'lg' && matchMedia(`screen and (min-width: 1200px)`).matches)
    );
  } else {
    const width = window.innerWidth;
    return breaks.some(
      item =>
        item === '*' ||
        (item === 'xs' && width < 768) ||
        (item === 'sm' && width >= 768 && width < 992) ||
        (item === 'md' && width >= 992 && width < 1200) ||
        (item === 'lg' && width >= 1200)
    );
  }
}

export function until(
  fn: () => Promise<any>,
  when: (ret: any) => boolean,
  getCanceler: (fn: () => any) => void,
  interval: number = 5000
) {
  let timer: ReturnType<typeof setTimeout>;
  let stoped: boolean = false;

  return new Promise((resolve, reject) => {
    let cancel = () => {
      clearTimeout(timer);
      stoped = true;
    };

    let check = async () => {
      try {
        const ret = await fn();

        if (stoped) {
          return;
        } else if (when(ret)) {
          stoped = true;
          resolve(ret);
        } else {
          timer = setTimeout(check, interval);
        }
      } catch (e) {
        reject(e);
      }
    };

    check();
    getCanceler && getCanceler(cancel);
  });
}

export function omitControls(
  controls: Array<any>,
  omitItems: Array<string>
): Array<any> {
  return controls.filter(
    control => !~omitItems.indexOf(control.name || control._name)
  );
}

export function isEmpty(thing: any) {
  if (isObject(thing) && Object.keys(thing).length) {
    return false;
  }

  return true;
}

/**
 * 基于时间戳的 uuid
 *
 * @returns uniqueId
 */
export const uuid = () => {
  return (+new Date()).toString(36);
};

// 参考 https://github.com/streamich/v4-uuid
const str = () =>
  (
    '00000000000000000' + (Math.random() * 0xffffffffffffffff).toString(16)
  ).slice(-16);

export const uuidv4 = () => {
  const a = str();
  const b = str();
  return (
    a.slice(0, 8) +
    '-' +
    a.slice(8, 12) +
    '-4' +
    a.slice(13) +
    '-a' +
    b.slice(1, 4) +
    '-' +
    b.slice(4)
  );
};

export interface TreeItem {
  children?: TreeArray;
  [propName: string]: any;
}
export interface TreeArray extends Array<TreeItem> { }

/**
 * 类似于 arr.map 方法，此方法主要针对类似下面示例的树形结构。
 * [
 *     {
 *         children: []
 *     },
 *     // 其他成员
 * ]
 *
 * @param {Tree} tree 树形数据
 * @param {Function} iterator 处理函数，返回的数据会被替换成新的。
 * @return {Tree} 返回处理过的 tree
 */
export function mapTree<T extends TreeItem>(
  tree: Array<T>,
  iterator: (item: T, key: number, level: number, paths: Array<T>) => T,
  level: number = 1,
  depthFirst: boolean = false,
  paths: Array<T> = []
) {
  return tree.map((item: any, index) => {
    if (depthFirst) {
      let children: TreeArray | undefined = item.children
        ? mapTree(
          item.children,
          iterator,
          level + 1,
          depthFirst,
          paths.concat(item)
        )
        : undefined;
      children && (item = { ...item, children: children });
      item = iterator(item, index, level, paths) || { ...(item as object) };
      return item;
    }

    item = iterator(item, index, level, paths) || { ...(item as object) };

    if (item.children && item.children.splice) {
      item.children = mapTree(
        item.children,
        iterator,
        level + 1,
        depthFirst,
        paths.concat(item)
      );
    }

    return item;
  });
}

/**
 * 遍历树
 * @param tree
 * @param iterator
 */
export function eachTree<T extends TreeItem>(
  tree: Array<T>,
  iterator: (item: T, key: number, level: number) => any,
  level: number = 1
) {
  tree.map((item, index) => {
    iterator(item, index, level);

    if (item.children) {
      eachTree(item.children, iterator, level + 1);
    }
  });
}

/**
 * 在树中查找节点。
 * @param tree
 * @param iterator
 */
export function findTree<T extends TreeItem>(
  tree: Array<T>,
  iterator: (item: T, key: number, level: number, paths: Array<T>) => any
): T | null {
  let result: T | null = null;

  everyTree(tree, (item, key, level, paths) => {
    if (iterator(item, key, level, paths)) {
      result = item;
      return false;
    }
    return true;
  });

  return result;
}
/**
 * 在树上查找节点，相同的逐一返回
 * @param tree
 * @param iterator
 */
export function findTreeList<T extends TreeItem>(
  tree: Array<T>,
  iterator: (item: T, key: number, level: number, paths: Array<T>) => any
): T[] | null {
  let result: T[] = [];
  everyTree(tree, (item, key, level, paths) => {
    if (iterator(item, key, level, paths)) {
      result.push(item);
    }
    if (item.children) {
      findTreeList(item.children, iterator)
    }
    return true;
  });
  return result;
}
/**
 * 在树中查找节点, 返回下标数组。
 * @param tree
 * @param iterator
 */
export function findTreeIndex<T extends TreeItem>(
  tree: Array<T>,
  iterator: (item: T, key: number, level: number, paths: Array<T>) => any
): Array<number> | undefined {
  let idx: Array<number> = [];

  findTree(tree, (item, index, level, paths) => {
    if (iterator(item, index, level, paths)) {
      idx = [index];

      paths = paths.concat();
      paths.unshift({
        children: tree
      } as any);

      for (let i = paths.length - 1; i > 0; i--) {
        const prev = paths[i - 1];
        const current = paths[i];
        idx.unshift(prev.children!.indexOf(current));
      }

      return true;
    }
    return false;
  });

  return idx.length ? idx : undefined;
}

export function getTree<T extends TreeItem>(
  tree: Array<T>,
  idx: Array<number> | number
): T | undefined | null {
  const indexes = Array.isArray(idx) ? idx.concat() : [idx];
  const lastIndex = indexes.pop()!;
  let list: Array<T> | null = tree;
  for (let i = 0, len = indexes.length; i < len; i++) {
    const index = indexes[i];
    if (!list![index]) {
      list = null;
      break;
    }
    list = list![index].children as any;
  }
  return list ? list[lastIndex] : undefined;
}

/**
 * 过滤树节点
 *
 * @param tree
 * @param iterator
 */
export function filterTree<T extends TreeItem>(
  tree: Array<T>,
  iterator: (item: T, key: number, level: number) => any,
  level: number = 1,
  depthFirst: boolean = false
) {
  if (depthFirst) {
    return tree
      .map(item => {
        let children: TreeArray | undefined = item.children
          ? filterTree(item.children, iterator, level + 1, depthFirst)
          : undefined;

        if (Array.isArray(children) && Array.isArray(item.children)) {
          item = { ...item, children: children };
        }

        return item;
      })
      .filter((item, index) => iterator(item, index, level));
  }

  return tree
    .filter((item, index) => iterator(item, index, level))
    .map(item => {
      if (item.children) {
        let children = filterTree(
          item.children,
          iterator,
          level + 1,
          depthFirst
        );

        if (Array.isArray(children) && Array.isArray(item.children)) {
          item = { ...item, children: children };
        }
      }
      return item;
    });
}

/**
 * 判断树中每个节点是否满足某个条件。
 * @param tree
 * @param iterator
 */
export function everyTree<T extends TreeItem>(
  tree: Array<T>,
  iterator: (
    item: T,
    key: number,
    level: number,
    paths: Array<T>,
    indexes: Array<number>
  ) => boolean,
  level: number = 1,
  paths: Array<T> = [],
  indexes: Array<number> = []
): boolean {
  return tree.every((item, index) => {
    const value: any = iterator(item, index, level, paths, indexes);

    if (value && item.children) {
      return everyTree(
        item.children,
        iterator,
        level + 1,
        paths.concat(item),
        indexes.concat(index)
      );
    }

    return value;
  });
}

/**
 * 判断树中是否有某些节点满足某个条件。
 * @param tree
 * @param iterator
 */
export function someTree<T extends TreeItem>(
  tree: Array<T>,
  iterator: (item: T, key: number, level: number, paths: Array<T>) => boolean
): boolean {
  let result = false;

  everyTree(tree, (item: T, key: number, level: number, paths: Array<T>) => {
    if (iterator(item, key, level, paths)) {
      result = true;
      return false;
    }
    return true;
  });

  return result;
}

/**
 * 将树打平变成一维数组，可以传入第二个参数实现打平节点中的其他属性。
 *
 * 比如：
 *
 * flattenTree([
 *     {
 *         id: 1,
 *         children: [
 *              { id: 2 },
 *              { id: 3 },
 *         ]
 *     }
 * ], item => item.id); // 输出位 [1, 2, 3]
 *
 * @param tree
 * @param mapper
 */
export function flattenTree<T extends TreeItem>(tree: Array<T>): Array<T>;
export function flattenTree<T extends TreeItem, U>(
  tree: Array<T>,
  mapper: (value: T, index: number) => U
): Array<U>;
export function flattenTree<T extends TreeItem, U>(
  tree: Array<T>,
  mapper?: (value: T, index: number) => U
): Array<U> {
  let flattened: Array<any> = [];
  eachTree(tree, (item, index) =>
    flattened.push(mapper ? mapper(item, index) : item)
  );
  return flattened;
}

/**
 * 操作树，遵循 imutable, 每次返回一个新的树。
 * 类似数组的 splice 不同的地方这个方法不修改原始数据，
 * 同时第二个参数不是下标，而是下标数组，分别代表每一层的下标。
 *
 * 至于如何获取下标数组，请查看 findTreeIndex
 *
 * @param tree
 * @param idx
 * @param deleteCount
 * @param ...items
 */
export function spliceTree<T extends TreeItem>(
  tree: Array<T>,
  idx: Array<number> | number,
  deleteCount: number = 0,
  ...items: Array<T>
): Array<T> {
  const list = tree.concat();
  if (typeof idx === 'number') {
    list.splice(idx, deleteCount, ...items);
  } else if (Array.isArray(idx) && idx.length) {
    idx = idx.concat();
    const lastIdx = idx.pop()!;
    let host = idx.reduce((list: Array<T>, idx) => {
      const child = {
        ...list[idx],
        children: list[idx].children ? list[idx].children!.concat() : []
      };
      list[idx] = child;
      return child.children;
    }, list);
    host.splice(lastIdx, deleteCount, ...items);
  }

  return list;
}

/**
 * 计算树的深度
 * @param tree
 */
export function getTreeDepth<T extends TreeItem>(tree: Array<T>): number {
  return Math.max(
    ...tree.map(item => {
      if (Array.isArray(item.children)) {
        return 1 + getTreeDepth(item.children);
      }

      return 1;
    })
  );
}

/**
 * 从树中获取某个值的所有祖先
 * @param tree
 * @param value
 */
export function getTreeAncestors<T extends TreeItem>(
  tree: Array<T>,
  value: T,
  includeSelf = false
): Array<T> | null {
  let ancestors: Array<T> | null = null;

  findTree(tree, (item, index, level, paths) => {
    if (item === value) {
      ancestors = paths;
      if (includeSelf) {
        ancestors.push(item);
      }
      return true;
    }
    return false;
  });

  return ancestors;
}

/**
 * 从树中获取某个值的上级
 * @param tree
 * @param value
 */
export function getTreeParent<T extends TreeItem>(tree: Array<T>, value: T) {
  const ancestors = getTreeAncestors(tree, value);
  return ancestors?.length ? ancestors[ancestors.length - 1] : null;
}

export function ucFirst(str?: string) {
  return typeof str === 'string'
    ? str.substring(0, 1).toUpperCase() + str.substring(1)
    : str;
}

export function lcFirst(str?: string) {
  return str ? str.substring(0, 1).toLowerCase() + str.substring(1) : '';
}

export function camel(str?: string) {
  return str
    ? str
      .split(/[\s_\-]/)
      .map((item, index) => (index === 0 ? lcFirst(item) : ucFirst(item)))
      .join('')
    : '';
}

export function getWidthRate(value: any, strictMode = false): number {
  if (typeof value === 'string' && /\bcol\-\w+\-(\d+)\b/.test(value)) {
    return parseInt(RegExp.$1, 10);
  }

  return strictMode ? 0 : value || 0;
}

export function getLevelFromClassName(
  value: string,
  defaultValue: string = 'default'
) {
  if (
    /\b(?:btn|text)-(link|primary|secondary|info|success|warning|danger|light|dark)\b/.test(
      value
    )
  ) {
    return RegExp.$1;
  }

  return defaultValue;
}


export function string2regExp(value: string, caseSensitive = false) {
  if (typeof value !== 'string') {
    throw new TypeError('Expected a string');
  }

  return new RegExp(
    value.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d'),
    !caseSensitive ? 'i' : ''
  );
}


export function pickEventsProps(props: any) {
  const ret: any = {};
  props &&
    Object.keys(props).forEach(
      key => /^on/.test(key) && (ret[key] = props[key])
    );
  return ret;
}

export const autobind = autobindMethod;

export const bulkBindFunctions = function <
  T extends {
    [propName: string]: any;
  }
>(context: T, funNames: Array<FunctionPropertyNames<T>>) {
  funNames.forEach(key => (context[key] = context[key].bind(context)));
};

export function sortArray<T extends any>(
  items: Array<T>,
  field: string,
  dir: -1 | 1
): Array<T> {
  return items.sort((a: any, b: any) => {
    let ret: number;
    const a1 = a[field];
    const b1 = b[field];

    if (typeof a1 === 'number' && typeof b1 === 'number') {
      ret = a1 < b1 ? -1 : a1 === b1 ? 0 : 1;
    } else {
      ret = String(a1).localeCompare(String(b1));
    }

    return ret * dir;
  });
}

// 只判断一层, 如果层级很深，form-data 也不好表达。
export function hasFile(object: any): boolean {
  return Object.keys(object).some(key => {
    let value = object[key];

    return (
      value instanceof File ||
      (Array.isArray(value) && value.length && value[0] instanceof File)
    );
  });
}

export function qsstringify(
  data: any,
  options: any = {
    arrayFormat: 'indices',
    encodeValuesOnly: true
  },
  keepEmptyArray?: boolean
) {
  // qs会保留空字符串。fix: Combo模式的空数组，无法清空。改为存为空字符串；只转换一层
  keepEmptyArray &&
    Object.keys(data).forEach((key: any) => {
      Array.isArray(data[key]) && !data[key].length && (data[key] = '');
    });
  return qs.stringify(data, options);
}

export function qsparse(
  data: string,
  options: any = {
    arrayFormat: 'indices',
    encodeValuesOnly: true,
    depth: 1000 // 默认是 5， 所以condition-builder只要来个条件组就会导致报错
  }
) {
  return qs.parse(data, options);
}

export function object2formData(
  data: any,
  options: any = {
    arrayFormat: 'indices',
    encodeValuesOnly: true
  },
  fd: FormData = new FormData()
): any {
  let fileObjects: any = [];
  let others: any = {};

  Object.keys(data).forEach(key => {
    const value = data[key];

    if (value instanceof File) {
      fileObjects.push([key, value]);
    } else if (
      Array.isArray(value) &&
      value.length &&
      value[0] instanceof File
    ) {
      value.forEach(value => fileObjects.push([`${key}[]`, value]));
    } else {
      others[key] = value;
    }
  });

  // 因为 key 的格式太多了，偷个懒，用 qs 来处理吧。
  qsstringify(others, options)
    .split('&')
    .forEach(item => {
      let parts = item.split('=');
      // form-data/multipart 是不需要 encode 值的。
      parts[0] && fd.append(parts[0], decodeURIComponent(parts[1]));
    });

  // Note: File类型字段放在后面，可以支持第三方云存储鉴权
  fileObjects.forEach((fileObject: any[]) =>
    fd.append(fileObject[0], fileObject[1], fileObject[1].name)
  );

  return fd;
}

export function chainFunctions(
  ...fns: Array<(...args: Array<any>) => void>
): (...args: Array<any>) => void {
  return (...args: Array<any>) =>
    fns.reduce(
      (ret: any, fn: any) =>
        ret === false
          ? false
          : typeof fn == 'function'
            ? fn(...args)
            : undefined,
      undefined
    );
}

export function chainEvents(props: any, schema: any) {
  const ret: any = {};

  Object.keys(props).forEach(key => {
    if (
      key.substr(0, 2) === 'on' &&
      typeof props[key] === 'function' &&
      typeof schema[key] === 'function' &&
      schema[key] !== props[key]
    ) {
      // 表单项里面的 onChange 很特殊，这个不要处理。
      if (props.formStore && key === 'onChange') {
        ret[key] = props[key];
      } else {
        ret[key] = chainFunctions(schema[key], props[key]);
      }
    } else {
      ret[key] = props[key];
    }
  });
  return ret;
}

export function mapObject(value: any, fn: Function): any {
  if (Array.isArray(value)) {
    return value.map(item => mapObject(item, fn));
  }
  if (isObject(value)) {
    let tmpValue = { ...value };
    Object.keys(tmpValue).forEach(key => {
      (tmpValue as PlainObject)[key] = mapObject(
        (tmpValue as PlainObject)[key],
        fn
      );
    });
    return tmpValue;
  }
  return fn(value);
}

export function loadScript(src: string) {
  return new Promise<void>((ok, fail) => {
    const script = document.createElement('script');
    script.onerror = reason => fail(reason);

    if (~src.indexOf('{{callback}}')) {
      const callbackFn = `loadscriptcallback_${uuid()}`;
      (window as any)[callbackFn] = () => {
        ok();
        delete (window as any)[callbackFn];
      };
      src = src.replace('{{callback}}', callbackFn);
    } else {
      script.onload = () => ok();
    }

    script.src = src;
    document.head.appendChild(script);
  });
}

export class SkipOperation extends Error { }

/**
 * 将例如像 a.b.c 或 a[1].b 的字符串转换为路径数组
 *
 * @param string 要转换的字符串
 */
export const keyToPath = (string: string) => {
  const result = [];

  if (string.charCodeAt(0) === '.'.charCodeAt(0)) {
    result.push('');
  }

  string.replace(
    new RegExp(
      '[^.[\\]]+|\\[(?:([^"\'][^[]*)|(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2)\\]|(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))',
      'g'
    ),
    (match, expression, quote, subString) => {
      let key = match;
      if (quote) {
        key = subString.replace(/\\(\\)?/g, '$1');
      } else if (expression) {
        key = expression.trim();
      }
      result.push(key);
      return '';
    }
  );

  return result;
};

/**

 * 检查对象是否有循环引用，来自 https://stackoverflow.com/a/34909127
 * @param obj
 */
function isCyclic(obj: any): boolean {
  const seenObjects: any = [];
  function detect(obj: any) {
    if (obj && typeof obj === 'object') {
      if (seenObjects.indexOf(obj) !== -1) {
        return true;
      }
      seenObjects.push(obj);
      for (var key in obj) {
        if (obj.hasOwnProperty(key) && detect(obj[key])) {
          return true;
        }
      }
    }
    return false;
  }
  return detect(obj);
}

function internalFindObjectsWithKey(obj: any, key: string) {
  let objects: any[] = [];
  for (const k in obj) {
    if (!obj.hasOwnProperty(k)) continue;
    if (k === key) {
      objects.push(obj);
    } else if (typeof obj[k] === 'object') {
      objects = objects.concat(internalFindObjectsWithKey(obj[k], key));
    }
  }
  return objects;
}

/**
 * 深度查找具有某个 key 名字段的对象，实际实现是 internalFindObjectsWithKey，这里包一层是为了做循环引用检测
 * @param obj
 * @param key
 */
export function findObjectsWithKey(obj: any, key: string) {
  // 避免循环引用导致死循环
  if (isCyclic(obj)) {
    return [];
  }
  return internalFindObjectsWithKey(obj, key);
}

let scrollbarWidth: number;

/**
 * 获取浏览器滚动条宽度 https://stackoverflow.com/a/13382873
 */

export function getScrollbarWidth() {
  if (typeof scrollbarWidth !== 'undefined') {
    return scrollbarWidth;
  }
  // Creating invisible container
  const outer = document.createElement('div');
  outer.style.visibility = 'hidden';
  outer.style.overflow = 'scroll'; // forcing scrollbar to appear
  // @ts-ignore
  outer.style.msOverflowStyle = 'scrollbar'; // needed for WinJS apps
  document.body.appendChild(outer);

  // Creating inner element and placing it in the container
  const inner = document.createElement('div');
  outer.appendChild(inner);

  // Calculating difference between container's full width and the child width
  scrollbarWidth = outer.offsetWidth - inner.offsetWidth;

  // Removing temporary elements from the DOM
  // @ts-ignore
  outer.parentNode.removeChild(outer);

  return scrollbarWidth;
}

function resolveValueByName(data: any, name?: string) {
  return isPureVariable(name)
    ? resolveVariableAndFilter(name, data)
    : resolveVariable(name, data);
}

// 统一的获取 value 值方法
export function getPropValue<
  T extends {
    value?: any;
    name?: string;
    data?: any;
    defaultValue?: any;
  }
>(props: T, getter?: (props: T) => any) {
  const { name, value, data, defaultValue } = props;
  return (
    value ?? getter?.(props) ?? resolveValueByName(data, name) ?? defaultValue
  );
}

// 检测 value 是否有变化，有变化就执行 onChange
export function detectPropValueChanged<
  T extends {
    value?: any;
    name?: string;
    data?: any;
    defaultValue?: any;
  }
>(
  props: T,
  prevProps: T,
  onChange: (value: any) => void,
  getter?: (props: T) => any
) {
  let nextValue: any;
  if (typeof props.value !== 'undefined') {
    props.value !== prevProps.value && onChange(props.value);
  } else if ((nextValue = getter?.(props)) !== undefined) {
    nextValue !== getter!(prevProps) && onChange(nextValue);
  } else if (
    typeof props.name === 'string' &&
    (nextValue = resolveValueByName(props.data, props.name)) !== undefined
  ) {
    nextValue !== resolveValueByName(prevProps.data, prevProps.name) &&
      onChange(nextValue);
  } else if (props.defaultValue !== prevProps.defaultValue) {
    onChange(props.defaultValue);
  }
}

// 去掉字符串中的 html 标签，不完全准确但效率比较高
export function removeHTMLTag(str: string) {
  return typeof str === 'string' ? str.replace(/<\/?[^>]+(>|$)/g, '') : str;
}

/**
 * 将路径格式的value转换成普通格式的value值
 *
 * @example
 *
 * 'a/b/c' => 'c';
 * {label: 'A/B/C', value: 'a/b/c'} => {label: 'C', value: 'c'};
 * 'a/b/c,a/d' => 'c,d';
 * ['a/b/c', 'a/d'] => ['c', 'd'];
 * [{label: 'A/B/C', value: 'a/b/c'},{label: 'A/D', value: 'a/d'}] => [{label: 'C', value: 'c'},{label: 'D', value: 'd'}]
 */
export function normalizeNodePath(
  value: any,
  enableNodePath: boolean,
  labelField: string = 'label',
  valueField: string = 'value',
  pathSeparator: string = '/',
  delimiter: string = ','
) {
  const nodeValueArray: any[] = [];
  const nodePathArray: any[] = [];
  const getLastNodeFromPath = (path: any) =>
    last(path ? path.toString().split(pathSeparator) : []);

  if (typeof value === 'undefined' || !enableNodePath) {
    return { nodeValueArray, nodePathArray };
  }

  // 尾节点为当前options中value值
  if (Array.isArray(value)) {
    value.forEach(nodePath => {
      if (nodePath && nodePath.hasOwnProperty(valueField)) {
        nodeValueArray.push({
          ...nodePath,
          [labelField]: getLastNodeFromPath(nodePath[labelField]),
          [valueField]: getLastNodeFromPath(nodePath[valueField])
        });
        nodePathArray.push(nodePath[valueField]);
      } else {
        nodeValueArray.push(getLastNodeFromPath(nodePath));
        nodePathArray.push(nodePath);
      }
    });
  } else if (typeof value === 'string') {
    value
      .toString()
      .split(delimiter)
      .forEach(path => {
        nodeValueArray.push(getLastNodeFromPath(path));
        nodePathArray.push(path);
      });
  } else {
    nodeValueArray.push({
      ...value,
      [labelField]: getLastNodeFromPath(value[labelField]),
      [valueField || 'value']: getLastNodeFromPath(value[valueField])
    });
    nodePathArray.push(value[valueField]);
  }

  return { nodeValueArray, nodePathArray };
}

// 主要用于排除点击输入框和链接等情况
export function isClickOnInput(e: React.MouseEvent<HTMLElement>) {
  const target: HTMLElement = e.target as HTMLElement;
  let formItem;
  if (
    !e.currentTarget.contains(target) ||
    ~['INPUT', 'TEXTAREA'].indexOf(target.tagName) ||
    ((formItem = target.closest(`button, a, [data-role="form-item"]`)) &&
      e.currentTarget.contains(formItem))
  ) {
    return true;
  }
  return false;
}


// 计算字符串 hash
export function hashCode(s: string): number {
  return s.split('').reduce((a, b) => {
    a = (a << 5) - a + b.charCodeAt(0);
    return a & a;
  }, 0);
}

/**
 * 遍历 schema
 * @param json
 * @param mapper
 */
export function JSONTraverse(
  json: any,
  mapper: (value: any, key: string | number, host: Object) => any
) {
  Object.keys(json).forEach(key => {
    const value: any = json[key];
    if (isPlainObject(value) || Array.isArray(value)) {
      JSONTraverse(value, mapper);
    } else {
      mapper(value, key, json);
    }
  });
}

export function convertArrayValueToMoment(
  value: number[],
  types: string[],
  mom: moment.Moment
): moment.Moment {
  if (value.length === 0) return mom;
  for (let i = 0; i < types.length; i++) {
    const type = types[i];
    // @ts-ignore
    mom.set(type, value[i]);
  }
  return mom;
}


export function getRange(min: number, max: number, step: number = 1) {
  const arr = [];
  for (let i = min; i <= max; i += step) {
    arr.push(i);
  }
  return arr;
}
// 数组去重
export function uniqueArr(arr: any[]) {
  let newArr: any[] = [];
  for (var i = 0; i < arr.length; i++) {
    if (newArr.indexOf(arr[i]) === -1) {
      newArr.push(arr[i]);
    }
  }
  return newArr;
}

export function handleActionCallback_10003(
  data: any,
  store: any, // store
  handleAction?: any,
  closeDialog?: Function, // 关闭某页面打开的dialog
  returnValue?: boolean,
  env?: RendererEnv
) {

  const is_specialStaus = data?.hasOwnProperty('status');

  let schemaBody;

  if (is_specialStaus) {
    schemaBody = {
      "title": "提示",
      "type": "dialog",
      "body": data?.showText,
      "actions": [
        {
          "actionType": "cancel",
          "label": "取消",
          "type": "action"
        }, {
          "close": true,
          "label": "确认",
          "type": "action",
          "api": data?.nextAddr,
          "actionType": "ajax",
          "onAction": handleAction,
          "level": "danger"
        }
      ]
    };
    store.setCurrentAction({
      type: 'button',
      actionType: 'dialog',
      dialog: schemaBody
    })
  } else {
    if (Object.keys(data)?.length === 0) {
      MSG._success('操作执行成功', env?.getModalContainer)
      return;
    }
    schemaBody = data;
    let dialogStyle = data?.style ? data?.style : {};
    store.setCurrentAction({
      type: 'button',
      actionType: 'dialog',
      dialog: {
        "title": data?.title ?? "弹框",
        "type": "dialog",
        "body": {
          "body": schemaBody,
          "type": "service",
          "name": data?.name
        },
        "actions": [],
        ...dialogStyle
      }
    })
  }
  store.openDialog({}, undefined, (confirmd: any) => {
    closeDialog!()
  });
  return returnValue
}

export function repeatCount(count: number, iterator: (index: number) => any) {
  let result: Array<any> = [];
  let index = 0;

  while (count--) {
    result.push(iterator(index++));
  }

  return result;
}

// 用来规范浮点数的
export const AdjustToFiexdFloatNumber = 0.00000001



Number.prototype.toFixed = function (s: number) {
  let str = '' + round(this as any, s)
  const decimalIndex = str.indexOf('.');
  // 如果没有小数点，添加小数点和足够的0
  if (decimalIndex === -1 && s > 0) {
    str += '.' + '0'.repeat(s);
  } else if (decimalIndex !== -1) {
    // 如果有小数点，添加足够的0
    var actualDecimalPlaces = str.length - decimalIndex - 1;
    str += '0'.repeat(Math.max(0, s - actualDecimalPlaces));
  }
  return str
}
export function numberFormatter(num: number | string, precision: number = 0) {
  const ZERO = 0;
  const number = +num;
  if (typeof number === 'number' && !isNaN(number)) {
    const regexp = precision ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(\d{3})+$)/g;
    return number.toFixed(precision).replace(regexp, '$1,');
  }
  return ZERO.toFixed(precision);
}
