<template>
  <view
    class="lime-echart"
    :style="customStyle"
    v-if="canvasId"
    ref="limeEchart"
    :aria-label="ariaLabel"
  >
    <!-- #ifndef APP-NVUE -->
    <canvas
      class="lime-echart__canvas"
      v-if="use2dCanvas"
      type="2d"
      :id="canvasId"
      :style="canvasStyle"
      :disable-scroll="isDisableScroll"
      @touchstart="touchStart"
      @touchmove="touchMove"
      @touchend="touchEnd"
    />
    <!-- <canvas
			class="lime-echart__canvas"
			v-else-if="isPC"
			:style="canvasStyle"
			:id="canvasId"
			:canvas-id="canvasId"
			:disable-scroll="isDisableScroll"
			@mousedown="touchStart"
			@mousemove="touchMove"
			@mouseup="touchEnd"
		/> -->
    <canvas
      class="lime-echart__canvas"
      v-else
      :width="nodeWidth"
      :height="nodeHeight"
      :style="canvasStyle"
      :canvas-id="canvasId"
      :id="canvasId"
      :disable-scroll="isDisableScroll"
      @touchstart="touchStart"
      @touchmove="touchMove"
      @touchend="touchEnd"
    />
    <view
      class="lime-echart__mask"
      v-if="isPC"
      @mousedown="touchStart"
      @mousemove="touchMove"
      @mouseup="touchEnd"
      @touchstart="touchStart"
      @touchmove="touchMove"
      @touchend="touchEnd"
    >
    </view>
    <canvas
      v-if="isOffscreenCanvas"
      :style="offscreenStyle"
      :canvas-id="offscreenCanvasId"
    ></canvas>
    <!-- #endif -->
    <!-- #ifdef APP-NVUE -->
    <web-view
      class="lime-echart__canvas"
      :id="canvasId"
      :style="canvasStyle"
      :webview-styles="webviewStyles"
      ref="webview"
      src="/uni_modules/lime-echart/static/nvue.html?v=1"
      @pagefinish="finished = true"
      @onPostMessage="onMessage"
    ></web-view>
    <!-- #endif -->
  </view>
</template>

<script>
  // #ifdef VUE3
  // #ifdef APP-PLUS
  global = {}
  // #endif
  // #endif
  // #ifndef APP-NVUE
  import { Canvas, setCanvasCreator, dispatch } from './canvas'
  import {
    wrapTouch,
    convertTouchesToArray,
    devicePixelRatio,
    sleep,
    canIUseCanvas2d,
    getRect,
  } from './utils'
  // #endif
  // #ifdef APP-NVUE
  import { base64ToPath, sleep } from './utils'
  import { Echarts } from './nvue'
  // #endif
  const charts = {}
  const echartsObj = {}
  export default {
    name: 'lime-echart',
    props: {
      // #ifdef MP-WEIXIN || MP-TOUTIAO
      type: {
        type: String,
        default: '2d',
      },
      // #endif
      // #ifdef APP-NVUE
      webviewStyles: Object,
      // hybrid: Boolean,
      // #endif
      customStyle: String,
      isDisableScroll: Boolean,
      isClickable: {
        type: Boolean,
        default: true,
      },
      enableHover: Boolean,
      beforeDelay: {
        type: Number,
        default: 30,
      },
    },
    data() {
      return {
        // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
        use2dCanvas: true,
        // #endif
        // #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
        use2dCanvas: false,
        // #endif
        ariaLabel: '图表',
        width: null,
        height: null,
        nodeWidth: null,
        nodeHeight: null,
        // canvasNode: null,
        config: {},
        inited: false,
        finished: false,
        file: '',
        platform: '',
        isPC: false,
        isDown: false,
        isOffscreenCanvas: false,
        offscreenWidth: 0,
        offscreenHeight: 0,
      }
    },
    computed: {
      canvasId() {
        return `lime-echart${(this._ && this._.uid) || this._uid}`
      },
      offscreenCanvasId() {
        return `${this.canvasId}_offscreen`
      },
      offscreenStyle() {
        return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`
      },
      canvasStyle() {
        return this.width && this.height
          ? 'width:' + this.width + 'px;height:' + this.height + 'px'
          : ''
      },
    },
    // #ifndef VUE3
    beforeDestroy() {
      this.clear()
      this.dispose()
      // #ifdef H5
      if (this.isPC) {
        document.removeEventListener('mousewheel', this.mousewheel)
      }
      // #endif
    },
    // #endif
    // #ifdef VUE3
    unmounted() {
      this.clear()
      this.dispose()
      // #ifdef H5
      if (this.isPC) {
        document.removeEventListener('mousewheel', this.mousewheel)
      }
      // #endif
    },
    // #endif
    created() {
      // #ifdef H5
      if (!('ontouchstart' in window)) {
        this.isPC = true
        document.addEventListener('mousewheel', this.mousewheel)
      }
      // #endif
      // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
      const { platform } = uni.getSystemInfoSync()
      this.isPC = /windows/i.test(platform)
      // #endif
      this.use2dCanvas = this.type === '2d' && canIUseCanvas2d()
    },
    mounted() {
      this.$nextTick(() => {
        this.$emit('finished')
      })
    },
    methods: {
      // #ifdef APP-NVUE
      onMessage(e) {
        const res = e?.detail?.data[0] || null
        if (res?.event) {
          const data = JSON.parse(res.data)
          if (res.event === 'inited') {
            this.inited = true
          } else if (res.event.startsWith('@')) {
            this.chart.dispatchAction(res.event.split('@')[1], data.options)
          }

          this.$emit(res.event, data)
        } else if (res?.file) {
          this.file = res.data
        } else if (!res[0] && JSON.stringify(res[0]) != '{}') {
          console.error(res)
        } else {
          console.log(...res)
        }
      },
      // #endif
      setChart(callback) {
        if (!this.chart) {
          console.warn(`组件还未初始化，请先使用 init`)
          return
        }
        if (typeof callback === 'function' && this.chart) {
          callback(this.chart)
        }
        // #ifdef APP-NVUE
        if (typeof callback === 'function') {
          this.$refs.webview.evalJs(
            `setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.chart.options)})`
          )
        }
        // #endif
      },
      setOption() {
        if (!this.chart || !this.chart.setOption) {
          console.warn(`组件还未初始化，请先使用 init`)
          return
        }
        this.chart.setOption(...arguments)
      },
      showLoading() {
        if (this.chart) {
          this.chart.showLoading(...arguments)
        }
      },
      hideLoading() {
        if (this.chart) {
          this.chart.hideLoading()
        }
      },
      clear() {
        if (this.chart) {
          this.chart.clear()
        }
      },
      dispose() {
        if (this.chart) {
          this.chart.dispose()
        }
      },
      resize(size) {
        if (size && size.width && size.height) {
          this.height = size.height
          this.width = size.width
          if (this.chart) {
            this.chart.resize(size)
          }
        } else {
          this.$nextTick(() => {
            uni
              .createSelectorQuery()
              .in(this)
              .select(`.lime-echart`)
              .boundingClientRect()
              .exec(res => {
                if (res) {
                  let { width, height } = res[0]
                  this.width = width = width || 300
                  this.height = height = height || 300
                  this.chart.resize({ width, height })
                }
              })
          })
        }
      },
      canvasToTempFilePath(args = {}) {
        // #ifndef APP-NVUE
        const { use2dCanvas, canvasId } = this
        return new Promise((resolve, reject) => {
          const copyArgs = Object.assign(
            {
              canvasId,
              success: resolve,
              fail: reject,
            },
            args
          )
          if (use2dCanvas) {
            delete copyArgs.canvasId
            copyArgs.canvas = this.canvasNode
          }
          uni.canvasToTempFilePath(copyArgs, this)
        })
        // #endif
        // #ifdef APP-NVUE
        this.file = ''
        this.$refs.webview.evalJs(`canvasToTempFilePath()`)
        return new Promise((resolve, reject) => {
          this.$watch('file', async file => {
            if (file) {
              const tempFilePath = await base64ToPath(file)
              resolve(args.success({ tempFilePath }))
            } else {
              reject(args.fail({ error: `` }))
            }
          })
        })
        // #endif
      },
      async init(echarts, ...args) {
        // #ifndef APP-NVUE
        if (arguments && arguments.length < 1) {
          console.error(
            '缺少参数：init(echarts, theme?:string, opts?: object, callback?: function)'
          )
          return
        }
        // #endif
        let theme = null,
          opts = {},
          callback

        Array.from(arguments).forEach(item => {
          if (typeof item === 'function') {
            callback = item
          }
          if (['string'].includes(typeof item)) {
            theme = item
          }
          if (typeof item === 'object') {
            opts = item
          }
        })

        if (this.beforeDelay) {
          await sleep(this.beforeDelay)
        }
        let config = await this.getContext()
        // #ifndef APP-NVUE
        setCanvasCreator(echarts, config)
        this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
        if (typeof callback === 'function') {
          callback(this.chart)
        } else {
          return this.chart
        }
        // #endif
        // #ifdef APP-NVUE
        this.chart = new Echarts(this.$refs.webview)
        this.$refs.webview.evalJs(`init(null, null, ${JSON.stringify(opts)}, ${theme})`)
        if (callback) {
          callback(this.chart)
        } else {
          return this.chart
        }
        // #endif
      },
      getContext() {
        // #ifdef APP-NVUE
        if (this.finished) {
          return Promise.resolve(this.finished)
        }
        return new Promise(resolve => {
          this.$watch('finished', val => {
            if (val) {
              resolve(this.finished)
            }
          })
        })
        // #endif
        // #ifndef APP-NVUE
        return getRect(`#${this.canvasId}`, {
          context: this,
          type: this.use2dCanvas ? 'fields' : 'boundingClientRect',
        }).then(res => {
          if (res) {
            let dpr = devicePixelRatio
            let { width, height, node } = res
            let canvas
            this.width = width = width || 300
            this.height = height = height || 300
            if (node) {
              const ctx = node.getContext('2d')
              canvas = new Canvas(ctx, this, true, node)
              this.canvasNode = node
            } else {
              // #ifdef MP-TOUTIAO
              dpr = !this.isPC ? devicePixelRatio : 1 // 1.25
              // #endif
              // #ifndef MP-ALIPAY || MP-TOUTIAO
              dpr = this.isPC ? devicePixelRatio : 1
              // #endif
              // #ifdef MP-ALIPAY || MP-LARK
              dpr = devicePixelRatio
              // #endif
              this.rect = res
              this.nodeWidth = width * dpr
              this.nodeHeight = height * dpr
              const ctx = uni.createCanvasContext(this.canvasId, this)
              canvas = new Canvas(ctx, this, false)
            }
            return { canvas, width, height, devicePixelRatio: dpr, node }
          } else {
            return {}
          }
        })
        // #endif
      },
      // #ifndef APP-NVUE
      getRelative(e, touches) {
        let { clientX, clientY } = e
        if (!(clientX && clientY) && touches && touches[0]) {
          clientX = touches[0].clientX
          clientY = touches[0].clientY
        }
        return {
          x: clientX - this.rect.left,
          y: clientY - this.rect.top,
          wheelDelta: e.wheelDelta || 0,
        }
      },
      getTouch(e, touches) {
        const { x } = (touches && touches[0]) || {}
        return x ? touches[0] : this.getRelative(e, touches)
      },
      touchStart(e) {
        this.isDown = true
        const next = () => {
          const touches = convertTouchesToArray(e.touches)
          if (this.chart) {
            const touch = this.getTouch(e, touches)
            this.startX = touch.x
            this.startY = touch.y
            this.startT = new Date()
            const handler = this.chart.getZr().handler
            dispatch.call(handler, 'mousedown', touch)
            dispatch.call(handler, 'mousemove', touch)
            handler.processGesture(wrapTouch(e), 'start')
            clearTimeout(this.endTimer)
          }
        }
        if (this.isPC) {
          getRect(`#${this.canvasId}`, { context: this }).then(res => {
            this.rect = res
            next()
          })
          return
        }
        next()
      },
      touchMove(e) {
        if (this.isPC && this.enableHover && !this.isDown) {
          this.isDown = true
        }
        const touches = convertTouchesToArray(e.touches)
        if (this.chart && this.isDown) {
          const handler = this.chart.getZr().handler
          dispatch.call(handler, 'mousemove', this.getTouch(e, touches))
          handler.processGesture(wrapTouch(e), 'change')
        }
      },
      touchEnd(e) {
        this.isDown = false
        if (this.chart) {
          const touches = convertTouchesToArray(e.changedTouches)
          const { x } = (touches && touches[0]) || {}
          const touch = (x ? touches[0] : this.getRelative(e, touches)) || {}
          const handler = this.chart.getZr().handler
          const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200
          dispatch.call(handler, 'mouseup', touch)
          handler.processGesture(wrapTouch(e), 'end')
          if (isClick) {
            dispatch.call(handler, 'click', touch)
          } else {
            this.endTimer = setTimeout(() => {
              dispatch.call(handler, 'mousemove', { x: 999999999, y: 999999999 })
              dispatch.call(handler, 'mouseup', { x: 999999999, y: 999999999 })
            }, 50)
          }
        }
      },
      // #endif
      // #ifdef H5
      mousewheel(e) {
        if (this.chart) {
          dispatch.call(this.chart.getZr().handler, 'mousewheel', this.getTouch(e))
        }
      },
      // #endif
    },
  }
</script>
<style>
  .lime-echart {
    position: relative;
    /* #ifndef APP-NVUE */
    width: 100%;
    height: 100%;
    /* #endif */
    /* #ifdef APP-NVUE */
    flex: 1;
    /* #endif */
  }
  .lime-echart__canvas {
    /* #ifndef APP-NVUE */
    width: 100%;
    height: 100%;
    /* #endif */
    /* #ifdef APP-NVUE */
    flex: 1;
    /* #endif */
  }
  /* #ifndef APP-NVUE */
  .lime-echart__mask {
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    z-index: 1;
  }
  /* #endif */
</style>
