<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
      :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/uvue.html?v=1"
      @pagefinish="finished = true"
      @onPostMessage="onMessage"
    ></web-view>
    <!-- #endif -->
  </view>
</template>

<script>
// @ts-nocheck
// #ifndef APP-NVUE
import { Canvas, setCanvasCreator, dispatch } from './canvas'
import {
  wrapTouch,
  convertTouchesToArray,
  devicePixelRatio,
  sleep,
  canIUseCanvas2d,
  getRect,
  getDeviceInfo,
} from './utils'
// #endif
// #ifdef APP-NVUE
import { base64ToPath, sleep } from './utils'
import { Echarts } from './nvue'
// #endif

/**
 * LimeChart 图表
 * @description 全端兼容的eCharts
 * @tutorial https://ext.dcloud.net.cn/plugin?id=4899

 * @property {String} customStyle 自定义样式
 * @property {String} type 指定 canvas 类型
 * @value 2d 使用canvas 2d，部分小程序支持
 * @value '' 使用原生canvas，会有层级问题
 * @value bottom right	不缩放图片，只显示图片的右下边区域
 * @property {Boolean} isDisableScroll	 
 * @property {number} beforeDelay = [30]  延迟初始化 (毫秒)
 * @property {Boolean} enableHover PC端使用鼠标悬浮

 * @event {Function} finished 加载完成触发
 */
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,
    },
    landscape: Boolean,
  },
  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: {
    rootStyle() {
      if (this.landscape) {
        return `transform: translate(-50%,-50%) rotate(90deg); top:50%; left:50%;`
      } else {
        return ``
      }
    },
    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.rootStyle +
        (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
  beforeUnmount() {
    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 } = getDeviceInfo()
    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 detail = e?.detail?.data[0] || null
      const data = detail?.data
      const key = detail?.event
      const options = data?.options
      const event = data?.event
      const file = detail?.file
      if (key == 'log' && data) {
        console.log(data)
      }
      if (event) {
        this.chart.dispatchAction(event.replace(/"/g, ''), options)
      }
      if (file) {
        thie.file = file
      }
    },
    // #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.isDisposed()) {
        this.chart.clear()
      }
    },
    dispose() {
      if (this.chart && !this.chart.isDisposed()) {
        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(() => {
          getRect('.lime-echart', this).then((res) => {
            if (res) {
              let { width, height } = res
              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 (args && args.length == 0 && !echarts) {
        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)
      try {
        this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts || {}))

        callback?.(this.chart)
        return this.chart
      } catch (e) {
        console.error('【lime-echarts】:', e)
        return null
      }
      // #endif
      // #ifdef APP-NVUE
      this.chart = new Echarts(this.$refs.webview)
      this.$refs.webview.evalJs(`init(null, null, ${JSON.stringify(opts)}, ${theme})`)
      callback?.(this.chart)
      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}`, this, this.use2dCanvas).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
            // #ifdef WEB
            dpr = 1
            // #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]) || {}
      const touch = x ? touches[0] : this.getRelative(e, touches)
      if (this.landscape) {
        ;[touch.x, touch.y] = [touch.y, this.height - touch.x]
      }
      return touch
    },
    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)) || {}
        if (this.landscape) {
          ;[touch.x, touch.y] = [touch.y, this.height - touch.x]
        }
        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>
