<script>
import Vue from 'vue'
export default {
  name: 'ToolTip',
  props: {
    title: String,
    prefixCls: {
      default: 'ant-tooltip',
    },
    placement: {
      default: 'top',
      validator: val => ['top', 'left', 'right', 'bottom', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight', 'leftTop', 'leftBottom', 'rightTop', 'rightBottom'].includes(val),
    },
    transitionName: {
      default: 'zoom-big-fast',
    },
    mouseEnterDelay: {
      default: 0.1,
    },
    mouseLeaveDelay: {
      default: 0.1,
    },
    arrowPointAtCenter: {
      default: false,
    },
    autoAdjustOverflow: {
      default: true,
    },
  },
  data () {
    return {
      vnode: null,
      visible: false,
      left: 0,
      top: 0,
      realPlacement: this.placement,
      t1: null,
      t2: null,
    }
  },
  computed: {
    classes () {
      const { prefixCls } = this
      return {
        [`${prefixCls}`]: true,
      }
    },
  },
  methods: {
    checkPosition (popup, text, placement) {
      const { top, left, bottom, right } = text
      const reg = /(top|bottom|left|right)(.*)/
      const [, abstractPos, suffix] = placement.match(reg)
      let ret = placement
      // we can change the position many times
      if (abstractPos === 'left' && left < popup.width) ret = 'right' + suffix
      if (abstractPos === 'right' && document.documentElement.clientWidth - right < popup.width) ret = 'left' + suffix
      if (abstractPos === 'top' && top < popup.height) ret = 'bottom' + suffix
      if (abstractPos === 'bottom' && document.documentElement.clientHeight - bottom < popup.height) ret = 'left' + suffix
      return ret
    },
    mountNode (callback) {
      if (this.vnode) {
        callback()
        return
      }
      const div = document.createElement('div')
      document.body.appendChild(div)
      const that = this
      const vnode = new Vue({
        data () {
          return {
            left: 0,
            top: 0,
          }
        },
        methods: {
          hideSelf (e) {
            if (that.t1) {
              clearTimeout(that.t1)
              that.t1 = null
            }
            if (that.mouseLeaveDelay) {
              that.t2 = window.setTimeout(() => {
                if (e.relatedTarget === that.$el) {
                  return
                }
                that.visible = false
              }, +that.mouseLeaveDelay * 1e3)
            }
          },
        },
        render (h) {
          return (
            <transition name={that.transitionName}>
              <div
                v-show={that.visible}
                class={`ant-tooltip ant-tooltip-placement-${that.realPlacement}`}
                style={{ left: this.left + 'px', top: this.top + 'px' }}
                onMouseleave={this.hideSelf}
              >
                <div class='ant-tooltip-content'>
                  <div class='ant-tooltip-arrow'/>
                  <div class='ant-tooltip-inner'>
                    <span>{that.title}</span>
                  </div>
                </div>
              </div>
            </transition>
          )
        },
      }).$mount(div)
      this.$nextTick(() => {
        this.vnode = vnode
        callback()
      })
    },
    onPopupAlign: (placement, domNode, target, align) => {
      if (!placement) {
        return
      }
      // 根据当前坐标设置动画点
      const rect = domNode.getBoundingClientRect()
      const transformOrigin = {
        top: '50%',
        left: '50%',
      }
      if (placement.indexOf('top') >= 0 || placement.indexOf('Bottom') >= 0) {
        transformOrigin.top = `${rect.height - align.offset[1]}px`
      } else if (placement.indexOf('Top') >= 0 || placement.indexOf('bottom') >= 0) {
        transformOrigin.top = `${-align.offset[1]}px`
      }
      if (placement.indexOf('left') >= 0 || placement.indexOf('Right') >= 0) {
        transformOrigin.left = `${rect.width - align.offset[0]}px`
      } else if (placement.indexOf('right') >= 0 || placement.indexOf('Left') >= 0) {
        transformOrigin.left = `${-align.offset[0]}px`
      }
      target.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`
    },
    addEventHandle (old, fn) {
      if (!old) {
        return fn
      } else if (Array.isArray(old)) {
        return old.indexOf(fn) > -1 ? old : old.concat(fn)
      } else {
        return old === fn ? old : [old, fn]
      }
    },
    computeOffset (popup, text, placement, scale) {
      let { width, height, top, left } = text
      //  you cant change the properties of DOMRect
      top += window.scrollY
      left += window.scrollX
      // FIXME: we can get the numbers from scale, but that's not what we really want
      const p = { width: popup.width / scale, height: popup.height / scale }
      const ret = { left, top }

      if (/top/.test(placement)) ret.top -= p.height
      if (/bottom/.test(placement)) ret.top += height
      if (/left/.test(placement)) ret.left -= p.width
      if (/right/.test(placement)) ret.left += width

      // FIXME: magic number 20 & 14 comes from the offset of triangle
      if (/Left/.test(placement)) {
        if (this.arrowPointAtCenter) ret.left += width / 2 - 20
      } else if (/Right/.test(placement)) {
        ret.left += (width - p.width)
        if (this.arrowPointAtCenter) ret.left -= width / 2 - 20
      } else if (/(top)|(bottom)/.test(placement)) {
        ret.left += (width - p.width) / 2
      }
      if (/Top/.test(placement)) {
        if (this.arrowPointAtCenter) ret.top += height / 2 - 14
      } else if (/Bottom/.test(placement)) {
        ret.top += (height - p.height)
        if (this.arrowPointAtCenter) ret.top -= height / 2 - 14
      } else if (/(left)|(right)/.test(placement)) {
        ret.top += (height - p.height) / 2
      }
      return ret
    },
    showNode () {
      this.mountNode(() => {
        this.visible = true
        this.$nextTick(() => {
          const popup = this.vnode.$el.getBoundingClientRect()
          const [, scale = 1] = window.getComputedStyle(this.vnode.$el).transform.match(/matrix\((.*?),/) || []
          const content = this.$el.getBoundingClientRect()
          const place = this.autoAdjustOverflow ? this.checkPosition(popup, content, this.placement, scale) : this.placement
          this.realPlacement = place
          const { left, top } = this.computeOffset(popup, content, place, scale)
          this.vnode.left = left
          this.vnode.top = top
        })
        this.onPopupAlign(this.realPlacement, this.$el, this.vnode.$el, { offset: [0, 0] })
      })
    },
    hideNode (e) {
      if (!this.vnode) return
      if (e.relatedTarget === this.vnode.$el) {
        return
      }
      this.visible = false
    },
    checkShow (e) {
      if (this.t2) {
        clearTimeout(this.t2)
        this.t2 = null
      }
      if (this.mouseEnterDelay) {
        this.t1 = window.setTimeout(() => {
          this.showNode(e)
        }, +this.mouseEnterDelay * 1e3)
      }
    },
    checkHide (e) {
      if (this.t1) {
        clearTimeout(this.t1)
        this.t1 = null
      }
      if (this.mouseLeaveDelay) {
        this.t2 = window.setTimeout(() => {
          this.hideNode(e)
        }, +this.mouseLeaveDelay * 1e3)
      }
    },
  },
  render (h) {
    const inner = this.$slots.default[0]
    inner.data = inner.data || {}
    inner.data.on = inner.data.on || {}
    inner.data.on.mouseenter = this.addEventHandle(inner.data.on.mouseenter, this.checkShow)
    inner.data.on.mouseleave = this.addEventHandle(inner.data.on.mouseleave, this.checkHide)

    return this.$slots.default[0]
  },
  updated () {
    if (!this.vnode) return
    const popup = this.vnode.$el.getBoundingClientRect()
    const [, scale = 1] = window.getComputedStyle(this.vnode.$el).transform.match(/matrix\((.*?),/) || []
    const content = this.$el.getBoundingClientRect()
    const { left, top } = this.computeOffset(popup, content, this.realPlacement, scale)
    this.vnode.left = left
    this.vnode.top = top
  },
  beforeDestroy () {
    if (!this.vnode) return
    this.vnode.$el.remove()
    this.vnode.$destroy()
  },
}
</script>
