<template>
  <view :class="`wd-circle ${customClass}`" :style="customStyle">
    <!-- #ifdef MP-WEIXIN -->
    <canvas :style="canvasStyle" :id="canvasId" :canvas-id="canvasId" type="2d"></canvas>
    <!-- #endif -->
    <!-- #ifndef MP-WEIXIN -->
    <canvas :width="canvasSize" :height="canvasSize" :style="canvasStyle" :id="canvasId" :canvas-id="canvasId"></canvas>
    <!-- #endif -->

    <view v-if="!text" class="wd-circle__text">
      <!-- 自定义提示内容 -->
      <slot></slot>
    </view>

    <text v-else class="wd-circle__text">
      {{ text }}
    </text>
  </view>
</template>
<script lang="ts">
export default {
  name: 'wd-circle',
  options: {
    addGlobalClass: true,
    virtualHost: true,
    styleIsolation: 'shared'
  }
}
</script>
<script lang="ts" setup>
import { computed, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, ref, watch } from 'vue'
import { addUnit, isObj, objToStyle, uuid } from '../common/util'
import { circleProps } from './types'
// #ifdef MP-WEIXIN
import { canvas2dAdapter } from '../common/canvasHelper'
// #endif

// 大于等于0且小于等于100
function format(rate: number) {
  return Math.min(Math.max(rate, 0), 100)
}
// 结束角度
const PERIMETER = 2 * Math.PI
// 开始角度
const BEGIN_ANGLE = -Math.PI / 2
const STEP = 1

const props = defineProps(circleProps)
const { proxy } = getCurrentInstance() as any

const progressColor = ref<string | CanvasGradient>('') // 进度条颜色

const currentValue = ref<number>(0) // 当前值
const interval = ref<any>(null) // 定时器
const pixelRatio = ref<number>(1) // 像素比
const canvasId = ref<string>(`wd-circle${uuid()}`) // canvasId
let ctx: UniApp.CanvasContext | null = null

// canvas渲染大小
const canvasSize = computed(() => {
  let size = props.size
  // #ifdef MP-ALIPAY
  size = size * pixelRatio.value
  // #endif

  return size
})

// 进度条宽度
const sWidth = computed(() => {
  let sWidth = props.strokeWidth
  // #ifdef MP-ALIPAY
  sWidth = sWidth * pixelRatio.value
  // #endif
  return sWidth
})

// Circle 样式
const canvasStyle = computed(() => {
  const style = {
    width: addUnit(props.size),
    height: addUnit(props.size)
  }
  return `${objToStyle(style)}`
})

// 监听目标数值变化
watch(
  () => props.modelValue,
  () => {
    reRender()
  },
  { immediate: true }
)

// 监听Circle大小变化
watch(
  () => props.size,
  () => {
    let timer = setTimeout(() => {
      drawCircle(currentValue.value)
      clearTimeout(timer)
    }, 50)
  },
  { immediate: false }
)

// 监听进度条颜色变化
watch(
  () => props.color,
  () => {
    drawCircle(currentValue.value)
  },
  { immediate: false, deep: true }
)

onBeforeMount(() => {
  pixelRatio.value = uni.getSystemInfoSync().pixelRatio
})

onMounted(() => {
  currentValue.value = props.modelValue
  drawCircle(currentValue.value)
})

onUnmounted(() => {
  clearTimeInterval()
})

/**
 * 获取canvas上下文
 */
function getContext() {
  return new Promise<UniApp.CanvasContext>((resolve) => {
    if (ctx) {
      return resolve(ctx)
    }
    // #ifndef MP-WEIXIN
    ctx = uni.createCanvasContext(canvasId.value, proxy)
    resolve(ctx)
    // #endif
    // #ifdef MP-WEIXIN
    uni
      .createSelectorQuery()
      .in(proxy)
      .select(`#${canvasId.value}`)
      .node((res) => {
        if (res && res.node) {
          const canvas = res.node
          ctx = canvas2dAdapter(canvas.getContext('2d') as CanvasRenderingContext2D)
          canvas.width = props.size * pixelRatio.value
          canvas.height = props.size * pixelRatio.value
          ctx.scale(pixelRatio.value, pixelRatio.value)
          resolve(ctx)
        }
      })
      .exec()
    // #endif
  })
}

/**
 * 设置canvas
 */
function presetCanvas(context: any, strokeStyle: string | CanvasGradient, beginAngle: number, endAngle: number, fill?: string) {
  let width = sWidth.value
  const position = canvasSize.value / 2
  if (!fill) {
    width = width / 2
  }
  const radius = position - width / 2
  context.strokeStyle = strokeStyle
  context.setStrokeStyle(strokeStyle)
  context.setLineWidth(width)
  context.setLineCap(props.strokeLinecap)

  context.beginPath()
  context.arc(position, position, radius, beginAngle, endAngle, !props.clockwise)
  context.stroke()
  if (fill) {
    context.setLineWidth(width)
    context.setFillStyle(fill)
    context.fill()
  }
}
/**
 * 渲染管道
 */
function renderLayerCircle(context: UniApp.CanvasContext) {
  presetCanvas(context, props.layerColor, 0, PERIMETER, props.fill)
}

/**
 * 渲染进度条
 */
function renderHoverCircle(context: UniApp.CanvasContext, formatValue: number) {
  // 结束角度
  const progress = PERIMETER * (formatValue / 100)
  const endAngle = props.clockwise ? BEGIN_ANGLE + progress : 3 * Math.PI - (BEGIN_ANGLE + progress)

  // 设置进度条颜色
  if (isObj(props.color)) {
    const LinearColor = context.createLinearGradient(canvasSize.value, 0, 0, 0)
    Object.keys(props.color)
      .sort((a, b) => parseFloat(a) - parseFloat(b))
      .map((key) => LinearColor.addColorStop(parseFloat(key) / 100, (props.color as Record<string, any>)[key]))
    progressColor.value = LinearColor
  } else {
    progressColor.value = props.color
  }
  presetCanvas(context, progressColor.value, BEGIN_ANGLE, endAngle)
}

/**
 * 渲染圆点
 * 进度值为0时渲染一个圆点
 */
function renderDot(context: UniApp.CanvasContext) {
  const strokeWidth = sWidth.value // 管道宽度=小圆点直径
  const position = canvasSize.value / 2 // 坐标
  // 设置进度条颜色
  if (isObj(props.color)) {
    const LinearColor = context.createLinearGradient(canvasSize.value, 0, 0, 0)
    Object.keys(props.color)
      .sort((a, b) => parseFloat(a) - parseFloat(b))
      .map((key) => LinearColor.addColorStop(parseFloat(key) / 100, (props.color as Record<string, any>)[key]))
    progressColor.value = LinearColor
  } else {
    progressColor.value = props.color
  }
  context.beginPath()
  context.arc(position, strokeWidth / 4, strokeWidth / 4, 0, PERIMETER)
  context.setFillStyle(progressColor.value)
  context.fill()
}

/**
 * 画圆
 */
function drawCircle(currentValue: number) {
  getContext().then((context) => {
    context.clearRect(0, 0, canvasSize.value, canvasSize.value)
    renderLayerCircle(context)

    const formatValue = format(currentValue)
    if (formatValue !== 0) {
      renderHoverCircle(context, formatValue)
    } else {
      renderDot(context)
    }
    context.draw()
  })
}

/**
 * Circle组件渲染
 * 当前进度值变化时重新渲染Circle组件
 */
function reRender() {
  // 动画通过定时器渲染
  if (props.speed <= 0 || props.speed > 1000) {
    drawCircle(props.modelValue)
    return
  }
  clearTimeInterval()
  currentValue.value = currentValue.value || 0
  const run = () => {
    interval.value = setTimeout(() => {
      if (currentValue.value !== props.modelValue) {
        if (Math.abs(currentValue.value - props.modelValue) < STEP) {
          currentValue.value = props.modelValue
        } else if (currentValue.value < props.modelValue) {
          currentValue.value += STEP
        } else {
          currentValue.value -= STEP
        }
        drawCircle(currentValue.value)
        run()
      } else {
        clearTimeInterval()
      }
    }, 1000 / props.speed)
  }
  run()
}
/**
 * 清除定时器
 */
function clearTimeInterval() {
  interval.value && clearTimeout(interval.value)
}
</script>

<style lang="scss" scoped>
@import './index.scss';
</style>
