text: import { isEqual, capitalize } from 'arche-ui/src/utils/util';
  import { isDef } from 'arche-ui/src/utils/shared';
  
  let uid = 0;
  
  export default class Node {
  
    constructor(data, config, parentNode) {
      this.data = data;
      this.config = config;
      this.parent = parentNode || null;
      this.level = !this.parent ? 1 : this.parent.level + 1;
      this.uid = uid++;
  
      this.initState();
      this.initChildren();
    }
  
    initState() {
      const { value: valueKey, label: labelKey } = this.config;
  
      this.value = this.data[valueKey];
      this.label = this.data[labelKey];
      this.pathNodes = this.calculatePathNodes();
      this.path = this.pathNodes.map(node => node.value);
      this.pathLabels = this.pathNodes.map(node => node.label);
  
      // lazy load
      this.loading = false;
      this.loaded = false;
    }
  
    initChildren() {
      const { config } = this;
      const childrenKey = config.children;
      const childrenData = this.data[childrenKey];
      this.hasChildren = Array.isArray(childrenData);
      this.children = (childrenData || []).map(child => new Node(child, config, this));
    }
  
    get isDisabled() {
      const { data, parent, config } = this;
      const disabledKey = config.disabled;
      const { checkStrictly } = config;
      return data[disabledKey] ||
        !checkStrictly && parent && parent.isDisabled;
    }
  
    get isLeaf() {
      const { data, loaded, hasChildren, children } = this;
      const { lazy, leaf: leafKey } = this.config;
      if (lazy) {
        const isLeaf = isDef(data[leafKey])
          ? data[leafKey]
          : (loaded ? !children.length : false);
        this.hasChildren = !isLeaf;
        return isLeaf;
      }
      return !hasChildren;
    }
  
    calculatePathNodes() {
      const nodes = [this];
      let parent = this.parent;
  
      while (parent) {
        nodes.unshift(parent);
        parent = parent.parent;
      }
  
      return nodes;
    }
  
    getPath() {
      return this.path;
    }
  
    getValue() {
      return this.value;
    }
  
    getValueByOption() {
      return this.config.emitPath
        ? this.getPath()
        : this.getValue();
    }
  
    getText(allLevels, separator) {
      return allLevels ? this.pathLabels.join(separator) : this.label;
    }
  
    isSameNode(checkedValue) {
      const value = this.getValueByOption();
      return this.config.multiple && Array.isArray(checkedValue)
        ? checkedValue.some(val => isEqual(val, value))
        : isEqual(checkedValue, value);
    }
  
    broadcast(event, ...args) {
      const handlerName = `onParent${capitalize(event)}`;
  
      this.children.forEach(child => {
        if (child) {
          // bottom up
          child.broadcast(event, ...args);
          child[handlerName] && child[handlerName](...args);
        }
      });
    }
  
    emit(event, ...args) {
      const { parent } = this;
      const handlerName = `onChild${capitalize(event)}`;
      if (parent) {
        parent[handlerName] && parent[handlerName](...args);
        parent.emit(event, ...args);
      }
    }
  
    onParentCheck(checked) {
      if (!this.isDisabled) {
        this.setCheckState(checked);
      }
    }
  
    onChildCheck() {
      const { children } = this;
      const validChildren = children.filter(child => !child.isDisabled);
      const checked = validChildren.length
        ? validChildren.every(child => child.checked)
        : false;
  
      this.setCheckState(checked);
    }
  
    setCheckState(checked) {
      const totalNum = this.children.length;
      const checkedNum = this.children.reduce((c, p) => {
        const num = p.checked ? 1 : (p.indeterminate ? 0.5 : 0);
        return c + num;
      }, 0);
  
      this.checked = checked;
      this.indeterminate = checkedNum !== totalNum && checkedNum > 0;
    }
  
    syncCheckState(checkedValue) {
      const value = this.getValueByOption();
      const checked = this.isSameNode(checkedValue, value);
  
      this.doCheck(checked);
    }
  
    doCheck(checked) {
      if (this.checked !== checked) {
        if (this.config.checkStrictly) {
          this.checked = checked;
        } else {
          // bottom up to unify the calculation of the indeterminate state
          this.broadcast('check', checked);
          this.setCheckState(checked);
          this.emit('check');
        }
      }
    }
  }
  