text: const Util = require('../../util')
  const Item = require('../../item')
  const deepMix = require('@antv/util/lib/deep-mix')
  
  const NODE = 'node'
  const EDGE = 'edge'
  const CFG_PREFIX = 'default'
  const MAPPER_SUFFIX = 'Mapper'
  const STATE_SUFFIX = 'stateStyles'
  const hasOwnProperty = Object.hasOwnProperty
  
  class ItemController {
    constructor(graph) {
      this.graph = graph
    }
    addItem(type, model) {
      const graph = this.graph
      const parent = graph.get(type + 'Group') || graph.get('group')
      const upperType = Util.upperFirst(type)
      let item
      let styles = graph.get(type + Util.upperFirst(STATE_SUFFIX)) || {}
      const defaultModel = graph.get(CFG_PREFIX + upperType)
      const mapper = graph.get(type + MAPPER_SUFFIX)
  
      if (mapper) {
        const mappedModel = mapper(model)
        if (mappedModel[STATE_SUFFIX]) {
          styles = mappedModel[STATE_SUFFIX]
          delete mappedModel[STATE_SUFFIX]
        }
  
        // 如果配置了 defaultEdge 或 defaultNode，则将默认配置的数据也合并进去
        model = deepMix({}, defaultModel, model, mappedModel)
      } else if (defaultModel) {
        // 很多布局会直接修改原数据模型，所以不能用 merge 的形式，逐个写入原 model 中
        Util.each(defaultModel, (val, cfg) => {
          if (!hasOwnProperty.call(model, cfg)) {
            if (Util.isObject(val)) {
              model[cfg] = Util.clone(val)
            } else {
              model[cfg] = defaultModel[cfg]
            }
          }
        })
      }
      graph.emit('beforeadditem', { type, model })
      if (type === EDGE) {
        let source = model.source
        let target = model.target
        if (source && Util.isString(source)) {
          source = graph.findById(source)
        }
        if (target && Util.isString(target)) {
          target = graph.findById(target)
        }
        if (!source || !target) {
          console.warn('The source or target node of edge ' + model.id + ' does not exist!')
          return
        }
        item = new Item[upperType]({
          model,
          source,
          target,
          styles,
          linkCenter: graph.get('linkCenter'),
          group: parent.addGroup()
        })
      } else {
        item = new Item[upperType]({
          model,
          styles,
          group: parent.addGroup()
        })
      }
      graph.get(type + 's').push(item)
      graph.get('itemMap')[item.get('id')] = item
      graph.autoPaint()
      graph.emit('afteradditem', { item, model })
      return item
    }
    updateItem(item, cfg) {
      const graph = this.graph
      if (Util.isString(item)) {
        item = graph.findById(item)
      }
      if (!item || item.destroyed) {
        return
      }
      // 如果修改了与映射属性有关的数据项，映射的属性相应也需要变化
      const mapper = graph.get(item.getType() + MAPPER_SUFFIX)
      const model = item.getModel()
      if (mapper) {
        const result = deepMix({}, model, cfg)
        const mappedModel = mapper(result)
        // 将 update 时候用户传入的参数与mapperModel做deepMix，以便复用之前设置的参数值
        const newModel = deepMix({}, model, mappedModel, cfg)
        if (mappedModel[STATE_SUFFIX]) {
          item.set('styles', newModel[STATE_SUFFIX])
          delete newModel[STATE_SUFFIX]
        }
        Util.each(newModel, (val, key) => {
          cfg[key] = val
        })
      } else {
        // merge update传进来的对象参数，model中没有的数据不做处理，对象和字符串值也不做处理，直接替换原来的
        Util.each(cfg, (val, key) => {
          if (model[key]) {
            if (Util.isObject(val) && !Util.isArray(val)) {
              cfg[key] = Util.mix({}, model[key], cfg[key])
            }
          }
        })
      }
      graph.emit('beforeupdateitem', { item, cfg })
      if (item.getType() === EDGE) {
        // 若是边要更新source || target, 为了不影响示例内部model，并且重新计算startPoint和endPoint，手动设置
        if (cfg.source) {
          let source = cfg.source
          if (Util.isString(source)) {
            source = graph.findById(source)
          }
          item.setSource(source)
        }
        if (cfg.target) {
          let target = cfg.target
          if (Util.isString(target)) {
            target = graph.findById(target)
          }
          item.setTarget(target)
        }
      }
      item.update(cfg)
      if (item.getType() === NODE) {
        const autoPaint = graph.get('autoPaint')
        graph.setAutoPaint(false)
        Util.each(item.getEdges(), edge => {
          graph.refreshItem(edge)
        })
        graph.setAutoPaint(autoPaint)
      }
      graph.autoPaint()
      graph.emit('afterupdateitem', { item, cfg })
    }
    removeItem(item) {
      const graph = this.graph
      if (Util.isString(item)) {
        item = graph.findById(item)
      }
      if (!item || item.destroyed) {
        return
      }
      graph.emit('beforeremoveitem', { item })
      const type = item.getType()
      const items = graph.get(item.getType() + 's')
      const index = items.indexOf(item)
      items.splice(index, 1)
      delete graph.get('itemMap')[item.get('id')]
      if (type === NODE) {
        // 若移除的是节点，需要将与之相连的边一同删除
        const edges = item.getEdges()
        for (let i = edges.length; i >= 0; i--) {
          graph.removeItem(edges[i])
        }
      }
      item.destroy()
      graph.autoPaint()
      graph.emit('afterremoveitem', { item })
    }
    setItemState(item, state, enabled) {
      const graph = this.graph
      if (item.hasState(state) === enabled) {
        return
      }
      graph.emit('beforeitemstatechange', { item, state, enabled })
      item.setState(state, enabled)
      graph.autoPaint()
      graph.emit('afteritemstatechange', { item, state, enabled })
    }
    clearItemStates(item, states) {
      const graph = this.graph
      if (Util.isString(item)) {
        item = graph.findById(item)
      }
      graph.emit('beforeitemstatesclear', { item, states })
      item.clearStates(states)
      graph.autoPaint()
      graph.emit('afteritemstatesclear', { item, states })
    }
    refreshItem(item) {
      const graph = this.graph
      if (Util.isString(item)) {
        item = graph.findById(item)
      }
      graph.emit('beforeitemrefresh', { item })
      item.refresh()
      graph.autoPaint()
      graph.emit('afteritemrefresh', { item })
    }
    changeItemVisibility(item, visible) {
      const self = this
      const graph = self.graph
      if (Util.isString(item)) {
        item = graph.findById(item)
      }
      graph.emit('beforeitemvisibilitychange', { item, visible })
      item.changeVisibility(visible)
      if (item.getType() === NODE) {
        const autoPaint = graph.get('autoPaint')
        graph.setAutoPaint(false)
        Util.each(item.getEdges(), edge => {
          // 若隐藏节点，则将与之关联的边也隐藏
          // 若显示节点，则将与之关联的边也显示，但是需要判断边两端的节点都是可见的
          if (visible && (!(edge.get('source').isVisible() && edge.get('target').isVisible()))) {
            return
          }
          self.changeItemVisibility(edge, visible)
        })
        graph.setAutoPaint(autoPaint)
      }
      graph.autoPaint()
      graph.emit('afteritemvisibilitychange', { item, visible })
    }
    destroy() {
      this.graph = null
      this.destroyed = true
    }
  }
  
  module.exports = ItemController
  