<script setup>
import { computed, getCurrentInstance, nextTick, onMounted, ref, watch } from "vue";

const defaultCanvasSize = 300;
const defaultRingCount = 10;

const props = defineProps({
  // canvas 唯一标识；不传时组件内部自动生成，避免多个靶面 canvas-id 冲突。
  canvasId: {
    type: String,
    default: "",
  },
  // 业务坐标半径，例如 20 表示命中点坐标范围为 -20 到 20。
  // 当前组件主要用它参与重绘判断，外层命中点定位也应使用同一半径。
  coordinateRadius: {
    type: Number,
    default: 20,
  },
  // 是否显示靶心十字辅助线。
  showCrosshair: {
    type: Boolean,
    default: false,
  },
  // 是否显示象限文字。
  showQuadrantLabels: {
    type: Boolean,
    default: false,
  },
  // 是否显示环数文字。
  showRingLabels: {
    type: Boolean,
    default: true,
  },
  // 象限文字配置，key 为 1/2/3/4。
  quadrantLabels: {
    type: Object,
    default: () => ({
      1: "1",
      2: "2",
      3: "3",
      4: "4",
    }),
  },
  // 高亮区域数组。
  // quadrant: 1/2/3/4，表示第几个象限。
  // rings: "all" 或环数数组，例如 [7, 8, 9, 10]。
  // scope: "box" 表示整象限矩形，"sector" 表示环形扇区。
  // style: 可覆盖高亮填充色、描边色、线宽比例。
  highlightAreas: {
    type: Array,
    default: () => [],
  },
  // 只绘制透明高亮层，不绘制完整靶纸；用于叠加在靶纸图片上。
  highlightOnly: {
    type: Boolean,
    default: false,
  },
  // 外部指定 canvas 绘制尺寸；用于让高亮层跟随靶图真实显示区域。
  canvasWidth: {
    type: Number,
    default: 0,
  },
  canvasHeight: {
    type: Number,
    default: 0,
  },
  // 靶纸样式覆盖配置，例如环数、环色、环线颜色、环数字体等。
  targetStyleConfig: {
    type: Object,
    default: () => ({}),
  },
  // 十字辅助线样式覆盖配置。
  crosshairStyle: {
    type: Object,
    default: () => ({}),
  },
  // 象限文字样式覆盖配置。
  quadrantLabelStyle: {
    type: Object,
    default: () => ({}),
  },
});

const instance = getCurrentInstance();
const localCanvasId = `target-canvas-${Math.random().toString(36).slice(2, 10)}`;
const currentCanvasId = computed(() => props.canvasId || localCanvasId);
const lastDrawKey = ref("");
const canvasSize = ref({
  width: defaultCanvasSize,
  height: defaultCanvasSize,
});

// 完整靶纸默认样式，调用方可以通过 targetStyleConfig 局部覆盖。
const defaultTargetStyleConfig = {
  ringCount: defaultRingCount,
  ringColors: {
    1: "#f8f8f3",
    2: "#f8f8f3",
    3: "#595959",
    4: "#595959",
    5: "#24aee0",
    6: "#24aee0",
    7: "#ff1f35",
    8: "#ff1f35",
    9: "#f7d34a",
    10: "#f7d34a",
  },
  ringLineColor: "rgba(150, 150, 150, 0.55)",
  ringLineWidthRatio: 0.0022,
  centerDotColor: "#ffffff",
  centerDotRadiusRatio: 0.0048,
  ringLabelFontRatio: 0.032,
  ringLabelDarkColor: "#111111",
  ringLabelLightColor: "#ffffff",
};

// 十字辅助线默认样式。
const defaultCrosshairStyle = {
  color: "rgba(20, 20, 20, 0.38)",
  lineWidthRatio: 0.0025,
};

// 象限文字默认样式。
const defaultQuadrantLabelStyle = {
  color: "#ffffff",
  fontSizeRatio: 0.045,
  offsetRatio: 0.78,
};

// 高亮区域默认样式。
const defaultHighlightStyle = {
  color: "rgba(254, 216, 71, 0.34)",
  strokeColor: "rgba(254, 216, 71, 0.82)",
  lineWidthRatio: 0.003,
};

// 合并默认靶纸样式和外部传入样式，ringColors 单独深合并。
const mergeTargetStyleConfig = () => ({
  ...defaultTargetStyleConfig,
  ...props.targetStyleConfig,
  ringColors: {
    ...defaultTargetStyleConfig.ringColors,
    ...(props.targetStyleConfig?.ringColors || {}),
  },
});

// 统一把外部传入值转成有效数字，非法值使用 fallback。
const getNumber = (value, fallback = 0) => {
  const numberValue = Number(value);
  return Number.isFinite(numberValue) ? numberValue : fallback;
};

// 获取指定环数的填充色，兼容数字 key 和字符串 key。
const getRingColor = (ring, config) => {
  return config.ringColors?.[ring] || config.ringColors?.[String(ring)] || "#ffffff";
};

// 规范化高亮环数配置；"all" 表示全部环，数组或单值会过滤非法环数。
const normalizeRings = (rings, ringCount) => {
  if (rings === "all") {
    return "all";
  }

  const rawRings = Array.isArray(rings) ? rings : [rings];
  return rawRings
    .map((ring) => Number(ring))
    .filter((ring) => Number.isInteger(ring) && ring >= 1 && ring <= ringCount);
};

// 获取象限对应的扇形弧度范围。
const getQuadrantAngles = (quadrant) => {
  const angleMap = {
    1: [Math.PI, Math.PI * 1.5],
    2: [Math.PI * 1.5, Math.PI * 2],
    3: [Math.PI * 0.5, Math.PI],
    4: [0, Math.PI * 0.5],
  };

  return angleMap[Number(quadrant)] || null;
};

// 获取象限对应的矩形区域，用于整象限高亮。
const getQuadrantBox = (quadrant, centerX, centerY, radius) => {
  const boxMap = {
    1: [centerX - radius, centerY - radius, radius, radius],
    2: [centerX, centerY - radius, radius, radius],
    3: [centerX - radius, centerY, radius, radius],
    4: [centerX, centerY, radius, radius],
  };

  return boxMap[Number(quadrant)] || null;
};

// 绘制实心圆，靶纸环区和中心点都会用到。
const drawCircle = (ctx, centerX, centerY, radius, fillColor) => {
  ctx.beginPath();
  ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
  ctx.setFillStyle(fillColor);
  ctx.fill();
};

// 绘制环形扇区，用于按象限高亮指定环数。
const drawAnnularSector = (
  ctx,
  centerX,
  centerY,
  innerRadius,
  outerRadius,
  startAngle,
  endAngle,
  fillColor,
  strokeColor = "",
  lineWidth = 0
) => {
  ctx.beginPath();
  ctx.arc(centerX, centerY, outerRadius, startAngle, endAngle);

  if (innerRadius > 0) {
    ctx.arc(centerX, centerY, innerRadius, endAngle, startAngle, true);
  } else {
    ctx.lineTo(centerX, centerY);
  }

  ctx.closePath();
  ctx.setFillStyle(fillColor);
  ctx.fill();

  if (strokeColor && lineWidth > 0) {
    ctx.setStrokeStyle(strokeColor);
    ctx.setLineWidth(lineWidth);
    ctx.stroke();
  }
};

// 从外到内绘制完整靶纸色环。
const drawTargetRings = (ctx, centerX, centerY, targetRadius, config) => {
  for (let ring = 1; ring <= config.ringCount; ring += 1) {
    const radius = targetRadius * ((config.ringCount + 1 - ring) / config.ringCount);
    drawCircle(ctx, centerX, centerY, radius, getRingColor(ring, config));
  }
};

// 绘制所有高亮区域，支持整象限矩形高亮和指定环数扇区高亮。
const drawHighlights = (ctx, centerX, centerY, targetRadius, config) => {
  props.highlightAreas.forEach((area = {}) => {
    const angles = getQuadrantAngles(area.quadrant);

    if (!angles) {
      return;
    }

    const highlightStyle = {
      ...defaultHighlightStyle,
      ...(area.style || {}),
    };
    const highlightLineWidth = Math.max(1, targetRadius * highlightStyle.lineWidthRatio);
    const rings = normalizeRings(area.rings || "all", config.ringCount);
    const scope = area.scope || (rings === "all" ? "box" : "sector");

    // 整象限默认画成矩形高亮，便于对应 1/2/3/4 号框训练提示。
    if (rings === "all" && scope === "box") {
      const box = getQuadrantBox(area.quadrant, centerX, centerY, targetRadius);
      if (!box) return;
      ctx.beginPath();
      ctx.rect(...box);
      ctx.setFillStyle(highlightStyle.color);
      ctx.fill();
      ctx.setStrokeStyle(highlightStyle.strokeColor);
      ctx.setLineWidth(highlightLineWidth);
      ctx.stroke();
      return;
    }

    const targetRings = rings === "all"
      ? Array.from({ length: config.ringCount }, (_, index) => index + 1)
      : rings;

    targetRings.forEach((ring) => {
      const innerRadius = targetRadius * ((config.ringCount - ring) / config.ringCount);
      const outerRadius = targetRadius * ((config.ringCount + 1 - ring) / config.ringCount);
      drawAnnularSector(
        ctx,
        centerX,
        centerY,
        innerRadius,
        outerRadius,
        angles[0],
        angles[1],
        highlightStyle.color,
        highlightStyle.strokeColor,
        highlightLineWidth
      );
    });
  });
};

// 绘制各环之间的分割线。
const drawRingLines = (ctx, centerX, centerY, targetRadius, config) => {
  const lineWidth = Math.max(1, targetRadius * config.ringLineWidthRatio);

  ctx.setStrokeStyle(config.ringLineColor);
  ctx.setLineWidth(lineWidth);

  for (let index = 1; index <= config.ringCount; index += 1) {
    const radius = targetRadius * (index / config.ringCount);
    ctx.beginPath();
    ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
    ctx.stroke();
  }
};

// 绘制靶心十字辅助线。
const drawCrosshair = (ctx, centerX, centerY, targetRadius) => {
  if (!props.showCrosshair) {
    return;
  }

  const style = {
    ...defaultCrosshairStyle,
    ...props.crosshairStyle,
  };

  ctx.beginPath();
  ctx.moveTo(centerX - targetRadius, centerY);
  ctx.lineTo(centerX + targetRadius, centerY);
  ctx.moveTo(centerX, centerY - targetRadius);
  ctx.lineTo(centerX, centerY + targetRadius);
  ctx.setStrokeStyle(style.color);
  ctx.setLineWidth(Math.max(1, targetRadius * style.lineWidthRatio));
  ctx.stroke();
};

// 绘制环数文字。
const drawRingLabels = (ctx, centerX, centerY, targetRadius, config) => {
  if (!props.showRingLabels) {
    return;
  }

  const ringWidth = targetRadius / config.ringCount;
  const fontSize = Math.max(10, targetRadius * config.ringLabelFontRatio);

  ctx.setFontSize(fontSize);
  ctx.setTextAlign("center");
  ctx.setTextBaseline("middle");

  for (let ring = config.ringCount; ring >= 1; ring -= 1) {
    const y = centerY + (config.ringCount - ring + 0.45) * ringWidth;
    const color = ring <= 2 ? config.ringLabelDarkColor : config.ringLabelLightColor;
    ctx.setFillStyle(color);
    ctx.fillText(String(ring), centerX, y);
  }
};

// 绘制象限文字。
const drawQuadrantLabels = (ctx, centerX, centerY, targetRadius) => {
  if (!props.showQuadrantLabels) {
    return;
  }

  const style = {
    ...defaultQuadrantLabelStyle,
    ...props.quadrantLabelStyle,
  };
  const offset = targetRadius * style.offsetRatio;
  const positions = {
    1: [centerX - offset, centerY - offset],
    2: [centerX + offset, centerY - offset],
    3: [centerX - offset, centerY + offset],
    4: [centerX + offset, centerY + offset],
  };

  ctx.setFontSize(Math.max(12, targetRadius * style.fontSizeRatio));
  ctx.setTextAlign("center");
  ctx.setTextBaseline("middle");
  ctx.setFillStyle(style.color);

  Object.entries(positions).forEach(([key, position]) => {
    const label = props.quadrantLabels?.[key] || props.quadrantLabels?.[Number(key)];
    if (label === undefined || label === null || label === "") return;
    ctx.fillText(String(label), position[0], position[1]);
  });
};

// 生成高亮区域的绘制 key，只保留真正影响画面的字段。
const getHighlightDrawKeyAreas = () => {
  return props.highlightAreas.map((area = {}) => ({
    quadrant: area.quadrant,
    rings: area.rings,
    scope: area.scope,
    style: area.style,
  }));
};

// 生成本次绘制状态的唯一 key，用于避免相同内容重复 draw。
const getDrawKey = (width, height) => {
  return JSON.stringify({
    width,
    height,
    coordinateRadius: props.coordinateRadius,
    showCrosshair: props.showCrosshair,
    showQuadrantLabels: props.showQuadrantLabels,
    showRingLabels: props.showRingLabels,
    highlightAreas: getHighlightDrawKeyAreas(),
    targetStyleConfig: props.targetStyleConfig,
    crosshairStyle: props.crosshairStyle,
    quadrantLabelStyle: props.quadrantLabelStyle,
    highlightOnly: props.highlightOnly,
  });
};

// 主绘制入口：根据 highlightOnly 决定画完整靶纸，还是只画透明高亮层。
const drawTarget = () => {
  const width = Math.max(getNumber(canvasSize.value.width, defaultCanvasSize), 1);
  const height = Math.max(getNumber(canvasSize.value.height, defaultCanvasSize), 1);
  const drawKey = getDrawKey(width, height);

  if (drawKey === lastDrawKey.value) {
    return;
  }

  const size = Math.min(width, height);
  const centerX = width / 2;
  const centerY = height / 2;
  const targetRadius = size / 2;
  const config = mergeTargetStyleConfig();
  const ctx = uni.createCanvasContext(currentCanvasId.value, instance?.proxy);

  ctx.clearRect(0, 0, width, height);

  if (!props.highlightOnly) {
    drawTargetRings(ctx, centerX, centerY, targetRadius, config);
  }

  drawHighlights(ctx, centerX, centerY, targetRadius, config);

  if (!props.highlightOnly) {
    drawRingLines(ctx, centerX, centerY, targetRadius, config);
    drawCircle(
      ctx,
      centerX,
      centerY,
      Math.max(1, targetRadius * config.centerDotRadiusRatio),
      config.centerDotColor
    );
    drawCrosshair(ctx, centerX, centerY, targetRadius);
    drawRingLabels(ctx, centerX, centerY, targetRadius, config);
    drawQuadrantLabels(ctx, centerX, centerY, targetRadius);
  }

  ctx.draw();
  lastDrawKey.value = drawKey;
};

const setCanvasSizeAndDraw = async (width, height) => {
  canvasSize.value = {
    width: width > 0 ? width : defaultCanvasSize,
    height: height > 0 ? height : width || defaultCanvasSize,
  };

  await nextTick();
  drawTarget();
};

// 读取 canvas 实际渲染尺寸后再绘制，保证小程序真机尺寸和坐标一致。
const measureAndDraw = () => {
  const propWidth = Math.round(getNumber(props.canvasWidth, 0));
  const propHeight = Math.round(getNumber(props.canvasHeight, 0));

  if (propWidth > 0 && propHeight > 0) {
    setCanvasSizeAndDraw(propWidth, propHeight);
    return;
  }

  const query = uni.createSelectorQuery().in(instance?.proxy);

  query
    .select(`#${currentCanvasId.value}`)
    .boundingClientRect(async (rect) => {
      const width = Math.round(getNumber(rect?.width, defaultCanvasSize));
      const height = Math.round(getNumber(rect?.height, width || defaultCanvasSize));

      await setCanvasSizeAndDraw(width, height);
    })
    .exec();
};

// 等待 Vue 完成 DOM 更新后重新测量和绘制。
const scheduleDraw = async () => {
  await nextTick();
  measureAndDraw();
};

watch(
  () => [
    props.coordinateRadius,
    props.showCrosshair,
    props.showQuadrantLabels,
    props.showRingLabels,
    props.highlightAreas,
    props.highlightOnly,
    props.canvasWidth,
    props.canvasHeight,
    props.quadrantLabels,
    props.targetStyleConfig,
    props.crosshairStyle,
    props.quadrantLabelStyle,
  ],
  scheduleDraw,
  {
    deep: true,
  }
);

onMounted(() => {
  setTimeout(measureAndDraw, 30);
});
</script>

<template>
  <canvas
    :id="currentCanvasId"
    class="target-canvas"
    :canvas-id="currentCanvasId"
    :width="canvasSize.width"
    :height="canvasSize.height"
  />
</template>

<style scoped>
.target-canvas {
  display: block;
  width: 100%;
  height: 100%;
}
</style>
