<script setup lang="ts">
import type { AiotDeviceApi } from '#/api/aiot/device';

import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';

interface Props {
  rois: AiotDeviceApi.Roi[];
  drawMode: string | null;
  selectedRoiId: string | null;
  snapUrl: string;
}

const props = withDefaults(defineProps<Props>(), {
  rois: () => [],
  drawMode: null,
  selectedRoiId: null,
  snapUrl: '',
});

const emit = defineEmits<{
  'draw-cancelled': [];
  'roi-deleted': [roiId: string];
  'roi-drawn': [data: { roi_type: string; coordinates: string }];
  'roi-selected': [roiId: string | null];
  'snap-status': [ok: boolean];
}>();

const wrapper = ref<HTMLDivElement>();
const canvas = ref<HTMLCanvasElement>();

let ctx: CanvasRenderingContext2D | null = null;
let canvasWidth = 0;
let canvasHeight = 0;

const polygonPoints = ref<{ x: number; y: number }[]>([]);
let mouseMovePoint: { x: number; y: number } | null = null;
const loading = ref(true);
const errorMsg = ref('');

watch(() => props.rois, () => redraw(), { deep: true });
watch(() => props.selectedRoiId, () => redraw());
watch(() => props.drawMode, (newVal) => {
  polygonPoints.value = [];
  mouseMovePoint = null;
  redraw();
  if (newVal && wrapper.value) {
    wrapper.value.focus();
  }
});
watch(() => props.snapUrl, () => {
  loading.value = true;
  errorMsg.value = '';
  nextTick(() => initCanvas());
});

let resizeObserver: ResizeObserver | null = null;

onMounted(() => {
  nextTick(() => {
    initCanvas();
    if (wrapper.value && typeof ResizeObserver !== 'undefined') {
      resizeObserver = new ResizeObserver(() => {
        if (wrapper.value && wrapper.value.clientWidth > 0) {
          initCanvas();
        }
      });
      resizeObserver.observe(wrapper.value);
    }
    window.addEventListener('resize', handleResize);
  });
});

onUnmounted(() => {
  window.removeEventListener('resize', handleResize);
  if (resizeObserver) {
    resizeObserver.disconnect();
    resizeObserver = null;
  }
});

function onImageLoad() {
  loading.value = false;
  emit('snap-status', true);
  nextTick(() => initCanvas());
}

function onImageError() {
  loading.value = false;
  errorMsg.value = '截图加载失败，请确认摄像头拉流地址是否有效';
  emit('snap-status', false);
  nextTick(() => initCanvas());
}

function getImageContentRect() {
  const img = wrapper.value?.querySelector('img');
  if (!img || !img.naturalWidth || !wrapper.value) {
    return {
      x: 0,
      y: 0,
      w: wrapper.value?.clientWidth ?? 0,
      h: wrapper.value?.clientHeight ?? 0,
    };
  }
  const cW = wrapper.value.clientWidth;
  const cH = wrapper.value.clientHeight;
  const imgRatio = img.naturalWidth / img.naturalHeight;
  const cRatio = cW / cH;
  let rW: number;
  let rH: number;
  if (imgRatio > cRatio) {
    rW = cW;
    rH = cW / imgRatio;
  } else {
    rH = cH;
    rW = cH * imgRatio;
  }
  return { x: (cW - rW) / 2, y: (cH - rH) / 2, w: rW, h: rH };
}

function initCanvas() {
  if (!canvas.value || !wrapper.value) return;
  const rect = getImageContentRect();
  canvasWidth = rect.w;
  canvasHeight = rect.h;
  canvas.value.width = canvasWidth;
  canvas.value.height = canvasHeight;
  canvas.value.style.left = `${rect.x}px`;
  canvas.value.style.top = `${rect.y}px`;
  canvas.value.style.width = `${rect.w}px`;
  canvas.value.style.height = `${rect.h}px`;
  ctx = canvas.value.getContext('2d');
  redraw();
}

function handleResize() {
  initCanvas();
}

function getCanvasPoint(e: MouseEvent) {
  const rect = canvas.value!.getBoundingClientRect();
  return {
    x: (e.clientX - rect.left) / canvasWidth,
    y: (e.clientY - rect.top) / canvasHeight,
  };
}

// ---- 鼠标事件 ----

function onMouseDown(e: MouseEvent) {
  if (e.button !== 0) return;
  const pt = getCanvasPoint(e);

  if (props.drawMode === 'polygon') {
    polygonPoints.value.push(pt);
    redraw();
    drawPolygonInProgress();
  } else if (!props.drawMode) {
    const clickedRoi = findRoiAtPoint(pt);
    emit('roi-selected', clickedRoi?.roiId ?? null);
  }
}

function onMouseMove(e: MouseEvent) {
  if (props.drawMode !== 'polygon' || polygonPoints.value.length === 0) return;
  mouseMovePoint = getCanvasPoint(e);
  redraw();
  drawPolygonInProgress();
}

function onDoubleClick() {
  if (props.drawMode === 'polygon' && polygonPoints.value.length >= 3) {
    finishPolygon();
  }
}

function onContextMenu(e: MouseEvent) {
  e.preventDefault();
  if (props.drawMode === 'polygon' && polygonPoints.value.length >= 3) {
    finishPolygon();
    return;
  }
  if (!props.drawMode) {
    const pt = getCanvasPoint(e);
    const roi = findRoiAtPoint(pt);
    if (roi?.roiId) {
      emit('roi-deleted', roi.roiId);
    }
  }
}

// ---- 键盘事件 ----

function onKeyDown(e: KeyboardEvent) {
  if (props.drawMode === 'polygon') {
    if (e.key === 'Escape') {
      polygonPoints.value = [];
      mouseMovePoint = null;
      emit('draw-cancelled');
      redraw();
    } else if ((e.ctrlKey && e.key === 'z') || e.key === 'Backspace') {
      e.preventDefault();
      if (polygonPoints.value.length > 0) {
        polygonPoints.value.pop();
        redraw();
        if (polygonPoints.value.length > 0) {
          drawPolygonInProgress();
        }
      }
    }
  }
}

// ---- 多边形完成（公开方法，供父组件调用） ----

function finishPolygon() {
  if (polygonPoints.value.length < 3) return;
  const points = polygonPoints.value.map((p) => ({ x: p.x, y: p.y }));
  emit('roi-drawn', {
    roi_type: 'polygon',
    coordinates: JSON.stringify(points),
  });
  polygonPoints.value = [];
  mouseMovePoint = null;
  redraw();
}

// ---- ROI 查找 ----

function findRoiAtPoint(pt: { x: number; y: number }) {
  for (let i = props.rois.length - 1; i >= 0; i--) {
    const roi = props.rois[i]!;
    try {
      const coords =
        typeof roi.coordinates === 'string'
          ? JSON.parse(roi.coordinates)
          : roi.coordinates;
      const type = roi.roiType;
      if (type === 'rectangle' || type === 'fullscreen') {
        if (
          pt.x >= coords.x &&
          pt.x <= coords.x + coords.w &&
          pt.y >= coords.y &&
          pt.y <= coords.y + coords.h
        ) {
          return roi;
        }
      } else if (type === 'polygon') {
        if (isPointInPolygon(pt, coords)) return roi;
      }
    } catch {
      /* skip */
    }
  }
  return null;
}

function isPointInPolygon(
  pt: { x: number; y: number },
  polygon: { x: number; y: number }[],
) {
  let inside = false;
  for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
    const xi = polygon[i]!.x;
    const yi = polygon[i]!.y;
    const xj = polygon[j]!.x;
    const yj = polygon[j]!.y;
    const intersect =
      yi > pt.y !== yj > pt.y &&
      pt.x < ((xj - xi) * (pt.y - yi)) / (yj - yi) + xi;
    if (intersect) inside = !inside;
  }
  return inside;
}

// ---- 绘制 ----

function redraw() {
  if (!ctx) return;
  ctx.clearRect(0, 0, canvasWidth, canvasHeight);

  props.rois.forEach((roi) => {
    try {
      const coords =
        typeof roi.coordinates === 'string'
          ? JSON.parse(roi.coordinates)
          : roi.coordinates;
      const color = roi.color || '#FF0000';
      const isSelected = roi.roiId === props.selectedRoiId;
      const type = roi.roiType;

      ctx!.strokeStyle = color;
      ctx!.lineWidth = isSelected ? 3 : 2;
      ctx!.fillStyle = `${color}33`;

      if (type === 'rectangle' || type === 'fullscreen') {
        const rx = coords.x * canvasWidth;
        const ry = coords.y * canvasHeight;
        const rw = coords.w * canvasWidth;
        const rh = coords.h * canvasHeight;
        ctx!.fillRect(rx, ry, rw, rh);
        ctx!.strokeRect(rx, ry, rw, rh);
        if (roi.name) {
          ctx!.fillStyle = color;
          ctx!.font = '12px Arial';
          ctx!.fillText(roi.name, rx + 4, ry + 14);
        }
      } else if (type === 'polygon') {
        ctx!.beginPath();
        coords.forEach((p: { x: number; y: number }, idx: number) => {
          const px = p.x * canvasWidth;
          const py = p.y * canvasHeight;
          if (idx === 0) ctx!.moveTo(px, py);
          else ctx!.lineTo(px, py);
        });
        ctx!.closePath();
        ctx!.fill();
        ctx!.stroke();
        if (roi.name && coords.length > 0) {
          ctx!.fillStyle = color;
          ctx!.font = '12px Arial';
          ctx!.fillText(
            roi.name,
            coords[0].x * canvasWidth + 4,
            coords[0].y * canvasHeight + 14,
          );
        }
      }
    } catch {
      /* skip */
    }
  });
}

function drawPolygonInProgress() {
  if (polygonPoints.value.length < 1 || !ctx) return;
  ctx.strokeStyle = '#00FF00';
  ctx.lineWidth = 2;
  ctx.setLineDash([5, 5]);
  ctx.beginPath();
  polygonPoints.value.forEach((p, idx) => {
    const px = p.x * canvasWidth;
    const py = p.y * canvasHeight;
    if (idx === 0) ctx!.moveTo(px, py);
    else ctx!.lineTo(px, py);
    ctx!.fillStyle = '#00FF00';
    ctx!.fillRect(px - 4, py - 4, 8, 8);
  });
  // 鼠标跟随线
  if (mouseMovePoint) {
    ctx.lineTo(
      mouseMovePoint.x * canvasWidth,
      mouseMovePoint.y * canvasHeight,
    );
  }
  // 首尾闭合预览线
  if (polygonPoints.value.length >= 3) {
    const first = polygonPoints.value[0]!;
    const last = mouseMovePoint || polygonPoints.value[polygonPoints.value.length - 1]!;
    ctx.moveTo(last.x * canvasWidth, last.y * canvasHeight);
    ctx.lineTo(first.x * canvasWidth, first.y * canvasHeight);
  }
  ctx.stroke();
  ctx.setLineDash([]);
}

defineExpose({ polygonPoints, finishPolygon, redraw, drawPolygonInProgress });
</script>

<template>
  <div ref="wrapper" class="roi-canvas-wrapper" tabindex="0" @keydown="onKeyDown">
    <img
      v-if="snapUrl"
      :src="snapUrl"
      class="bg-image"
      @load="onImageLoad"
      @error="onImageError"
    />
    <div v-else class="video-placeholder">
      <span v-if="loading">正在截取画面...</span>
      <span v-else>{{ errorMsg || '暂无画面' }}</span>
    </div>
    <canvas
      ref="canvas"
      class="roi-overlay"
      :class="{ drawing: drawMode === 'polygon' }"
      @mousedown="onMouseDown"
      @mousemove="onMouseMove"
      @dblclick="onDoubleClick"
      @contextmenu="onContextMenu"
    />
    <!-- 绘制中浮动提示条 -->
    <div v-if="drawMode === 'polygon'" class="draw-hint-bar">
      单击添加顶点 | 双击或右键完成选区 | Esc取消 | Ctrl+Z撤销
    </div>
  </div>
</template>

<style scoped>
.roi-canvas-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 400px;
  background: #000;
  outline: none;
}

.bg-image {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

.video-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #666;
  font-size: 18px;
  background: #1a1a2e;
}

.roi-overlay {
  position: absolute;
}

.roi-overlay.drawing {
  cursor: crosshair;
}

.draw-hint-bar {
  position: absolute;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.75);
  color: #fff;
  padding: 6px 16px;
  border-radius: 4px;
  font-size: 13px;
  white-space: nowrap;
  pointer-events: none;
  z-index: 10;
}
</style>
