<template>
  <view :class="`wd-circle ${customClass}`">
    <canvas :width="canvasSize" :height="canvasSize" :style="style" :id="canvasId" :canvas-id="canvasId"></canvas>
    <view v-if="!text" class="wd-circle__text">
      <!-- 自定义提示内容 -->
      <slot></slot>
    </view>
    <!-- #ifdef MP-WEIXIN -->
    <cover-view v-else class="wd-circle__text">
      {{ text }}
    </cover-view>
    <!-- #endif -->
    <!-- #ifndef MP-WEIXIN -->
    <!-- eslint-disable-next-line vue/valid-v-else -->
    <text v-else class="wd-circle__text">
      {{ text }}
    </text>
    <!-- #endif -->
  </view>
</template>
<script lang="ts">
export default {
  name: 'wd-circle',
  options: {
    addGlobalClass: true,
    virtualHost: true,
    styleIsolation: 'shared'
  }
}
</script>
<script lang="ts" setup>
// Circle 环形进度条
import { computed, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, ref, watch } from 'vue'
import { addUnit, isObj, objToStyle, uuid } from '../common/util'

// 大于等于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
// 进度条端点的形状，可选值为 "butt" | "round" | "square"
type StrokeLinecapType = 'butt' | 'round' | 'square'
interface Props {
  // 当前进度
  modelValue: number
  // 自定义class
  customClass?: string
  // 自定义style
  customStyle?: string
  // 目标进度
  rate?: number | string
  // 圆环直径，默认单位为 px
  size?: number
  // 进度条颜色，传入对象格式可以定义渐变色
  color?: string | Record<string, string>
  // 轨道颜色
  layerColor?: string
  // 填充颜色
  fill?: string
  // 动画速度（单位为 rate/s）
  speed?: number
  // 文字
  text?: string
  // 进度条宽度 单位px
  strokeWidth?: number
  // 进度条端点的形状，可选值为 "butt" | "round" | "square"
  strokeLinecap?: StrokeLinecapType
  // 是否顺时针增加
  clockwise?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  // 当前进度
  modelValue: 0,
  // 自定义class
  customClass: '',
  // 自定义style
  customStyle: '',
  // 目标进度
  rate: 100,
  // 圆环直径，默认单位为 px
  size: 100,
  // 进度条颜色，传入对象格式可以定义渐变色
  color: '#4d80f0',
  // 轨道颜色
  layerColor: '#EBEEF5',
  // 填充颜色
  fill: '#ffffff',
  // 动画速度（单位为 rate/s）
  speed: 50,
  // 进度条宽度 默认单位px
  strokeWidth: 10,
  // 进度条端点的形状
  strokeLinecap: 'round',
  // 是否顺时针增加
  clockwise: true
})

const progressColor = ref<string | CanvasGradient>('') // 进度条颜色
const pixel = ref<number>(1) // 设备像素比
const currentValue = ref<number>(0) // 当前值
const interval = ref<any>(null) // 定时器
const canvasId = ref<string>(uuid()) // canvasId
let ctx: UniApp.CanvasContext | null = null
// canvas渲染大小
const canvasSize = computed(() => {
  return props.size * pixel.value
})

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

// 监听目标数值变化
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 }
)

// 监听轨道颜色变化
watch(
  () => props.layerColor,
  () => {
    drawCircle(currentValue.value)
  },
  { immediate: false }
)

// 监听轨道宽度
watch(
  () => props.strokeWidth,
  () => {
    drawCircle(currentValue.value)
  },
  { immediate: false }
)

// 监听轨道展示方向
watch(
  () => props.clockwise,
  () => {
    drawCircle(currentValue.value)
  },
  { immediate: false }
)

// #ifdef MP-ALIPAY
onBeforeMount(() => {
  pixel.value = uni.getSystemInfoSync().pixelRatio
})
// #endif

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

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

const { proxy } = getCurrentInstance() as any
/**
 * 获取canvas上下文
 */
function getContext() {
  if (!ctx) {
    ctx = uni.createCanvasContext(canvasId.value, proxy)
  }
  return Promise.resolve(ctx)
}

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

  context.beginPath()
  context.arc(position, position, radius, beginAngle, endAngle, !props.clockwise)
  context.stroke()
  if (fill) {
    context.setLineWidth(strokeWidth)
    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 canvasSize = props.size * pixel.value
  // 结束角度
  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, 0, 0, 0)
    Object.keys(props.color)
      .sort((a, b) => parseFloat(a) - parseFloat(b))
      .map((key) => LinearColor.addColorStop(parseFloat(key) / 100, props.color[key]))
    progressColor.value = LinearColor
  } else {
    progressColor.value = props.color
  }
  presetCanvas(context, progressColor.value, BEGIN_ANGLE, endAngle)
}

/**
 * 渲染圆点
 * 进度值为0时渲染一个圆点
 */
function renderDot(context: UniApp.CanvasContext) {
  const canvasSize = props.size * pixel.value
  const strokeWidth = props.strokeWidth * pixel.value // 管道宽度=小圆点直径
  const position = canvasSize / 2 // 坐标
  // 设置进度条颜色
  if (isObj(props.color)) {
    const LinearColor = context.createLinearGradient(canvasSize, 0, 0, 0)
    Object.keys(props.color)
      .sort((a, b) => parseFloat(a) - parseFloat(b))
      .map((key) => LinearColor.addColorStop(parseFloat(key) / 100, props.color[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) {
  const canvasSize = props.size * pixel.value
  getContext().then((context) => {
    context.clearRect(0, 0, canvasSize, canvasSize)
    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>
