<script setup lang="ts">
/**
 * 后台柱状图：金色柱 + 暗色网格，用于热门文章等排行。
 * 与折线图相同：首帧宽度可能为 0，需原生宽高 + 重试，否则小程序上空白。
 * getCurrentInstance 仅在 setup/同步生命周期有效，必须在此处缓存 proxy，
 * 否则 nextTick/setTimeout 里 .in(null) 会抛 $scope of null。
 */
import { getCurrentInstance, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'

const props = withDefaults(defineProps<{
  categories?: string[]
  values?: number[]
  height?: number
}>(), {
  categories: () => [],
  values: () => [],
  height: 220,
})

/** setup 阶段同步缓存，供异步 draw 使用 */
const compProxy = getCurrentInstance()?.proxy as never
const canvasId = `bar-${Math.random().toString(36).slice(2, 8)}`
/** 原生 canvas 缓冲像素宽（含 dpr） */
const canvasW = ref(300)
const canvasH = ref(220)
let retryTimer: ReturnType<typeof setTimeout> | null = null
let retryCount = 0
const MAX_RETRY = 5

/** 测量失败时用窗口宽度减去边距估算逻辑宽度；优先 getWindowInfo */
function fallbackWidth(): number {
  try {
    const win = typeof uni.getWindowInfo === 'function' ? uni.getWindowInfo() : null
    if (win?.windowWidth) return Math.max(200, win.windowWidth - 48)
    const sys = uni.getSystemInfoSync()
    return Math.max(200, (sys.windowWidth || 375) - 48)
  } catch {
    return 300
  }
}

function getDpr(): number {
  try {
    const device = typeof uni.getDeviceInfo === 'function' ? uni.getDeviceInfo() : null
    if (device && 'pixelRatio' in device && Number(device.pixelRatio)) {
      return Number(device.pixelRatio)
    }
    return uni.getSystemInfoSync().pixelRatio || 2
  } catch {
    return 2
  }
}

/**
 * 构建选择器：有组件实例则限定在组件内，否则页面级查询（canvasId 已全局唯一）。
 * 禁止对 null 调用 .in()，否则 mp-weixin 读 $scope 直接报错。
 */
function createQuery() {
  const q = uni.createSelectorQuery()
  if (compProxy) return q.in(compProxy)
  return q
}

/** 在逻辑像素坐标下绘制柱状图 */
function paint(ctx: UniApp.CanvasContext, w: number, h: number, dpr: number) {
  const cats = props.categories
  const vals = props.values
  ctx.scale(dpr, dpr)
  const padL = 12
  const padR = 12
  const padT = 16
  const padB = 40
  const plotW = w - padL - padR
  const plotH = h - padT - padB
  const max = Math.max(1, ...vals)
  const n = cats.length
  const gap = 8
  const barW = Math.max(8, (plotW - gap * (n + 1)) / n)

  ctx.clearRect(0, 0, w, h)
  ctx.setStrokeStyle('rgba(255,255,255,0.08)')
  for (let i = 0; i <= 3; i++) {
    const y = padT + (plotH * i) / 3
    ctx.beginPath()
    ctx.moveTo(padL, y)
    ctx.lineTo(w - padR, y)
    ctx.stroke()
  }

  vals.forEach((v, i) => {
    const bh = (v / max) * plotH
    const x = padL + gap + i * (barW + gap)
    const y = padT + plotH - bh
    ctx.setFillStyle('rgba(212,179,131,0.85)')
    ctx.fillRect(x, y, barW, bh)
    ctx.setFillStyle('rgba(136,136,136,1)')
    ctx.setFontSize(9)
    ctx.fillText(String(cats[i] || '').slice(0, 4), x, h - 12)
  })
  ctx.draw()
}

/**
 * 测量并绘制；宽度为 0 时 fallback + 短延迟重试；
 * 先写原生宽高再 nextTick 绘制。
 */
const draw = (isRetry = false) => {
  const cats = props.categories
  const vals = props.values
  if (!cats.length || !vals.length) return
  if (!isRetry) retryCount = 0

  createQuery()
    .select(`#${canvasId}`)
    .boundingClientRect((rect) => {
      const box = Array.isArray(rect) ? rect[0] : rect
      let w = box?.width || 0
      const measuredOk = !!w
      if (!w) w = fallbackWidth()
      if (!measuredOk && retryCount < MAX_RETRY) {
        retryCount += 1
        if (retryTimer) clearTimeout(retryTimer)
        retryTimer = setTimeout(() => draw(true), 80)
      } else if (measuredOk) {
        retryCount = 0
      }
      if (!w) return

      const h = props.height
      const dpr = getDpr()
      canvasW.value = Math.floor(w * dpr)
      canvasH.value = Math.floor(h * dpr)
      nextTick(() => {
        const ctx = uni.createCanvasContext(canvasId, compProxy)
        paint(ctx, w, h, dpr)
      })
    })
    .exec()
}

onMounted(() => nextTick(() => draw()))
onUnmounted(() => {
  if (retryTimer) clearTimeout(retryTimer)
})
watch(() => [props.categories, props.values], () => nextTick(() => draw()), { deep: true })
</script>

<template>
  <canvas
    :id="canvasId"
    :canvas-id="canvasId"
    class="chart"
    :width="canvasW"
    :height="canvasH"
    :style="{ width: '100%', height: `${height}px` }"
  />
</template>

<style scoped>
.chart { width: 100%; display: block; }
</style>
