<template>
    <div class="container">
        <canvas id="map-canvas"></canvas>
    </div>
</template>

<script setup>
import { onMounted, onUnmounted } from 'vue';

// ───────────────────────────────────────────────
// 1. 常量：颜色定义
// ───────────────────────────────────────────────

/**
 * 0xAARRGGBB 整数颜色解码为 {r, g, b, a} 对象
 * @param {number} argbInt - 32位 ARGB 整数，如 0xCCFF0040
 * @returns {{r:number, g:number, b:number, a:number}|null} RGBA 分量 (0~255)，无效输入返回 null
 */
function decodeArgbInteger(argbInt) {
    if (typeof argbInt !== 'number') return null;               // 非数字类型直接返回
    return {
        a: (argbInt >>> 24) & 0xFF,                               // 高8位：Alpha 透明度
        r: (argbInt >>> 16) & 0xFF,                               // 次高8位：Red
        g: (argbInt >>> 8) & 0xFF,                               // 次低8位：Green
        b: argbInt & 0xFF,                               // 低8位：Blue
    };
}

/**
 * 将 decodeArgbInteger 的结果转为 CSS rgba() 字符串
 * @param {{r,g,b,a}} colorObj - RGBA 分量对象
 * @param {number} [alphaOverride] - 可选，覆盖 alpha 值（0~1）
 * @returns {string|null} CSS rgba 字符串，如 "rgba(255,0,64,0.3)"
 */
function toCssRgba(colorObj, alphaOverride) {
    if (!colorObj) return null;                                  // 空对象返回 null
    const alpha = alphaOverride !== undefined                    // 如果提供了覆盖值
        ? alphaOverride                                            // 使用覆盖值
        : colorObj.a / 255;                                        // 否则将 0~255 映射到 0~1
    return `rgba(${colorObj.r},${colorObj.g},${colorObj.b},${alpha})`;
}

/**
 * 6位十六进制颜色字符串转为 CSS rgba 字符串
 * @param {string} hex - 如 "#ff6e40"
 * @param {number} alpha - 透明度 0~1
 * @returns {string} CSS rgba 字符串
 */
function hexToCssRgba(hexString, alpha) {
    const r = parseInt(hexString.slice(1, 3), 16);               // 提取 R 分量
    const g = parseInt(hexString.slice(3, 5), 16);               // 提取 G 分量
    const b = parseInt(hexString.slice(5, 7), 16);               // 提取 B 分量
    return `rgba(${r},${g},${b},${alpha})`;
}

// ───────────────────────────────────────────────
// 2. 应用全局状态
// ───────────────────────────────────────────────

/**
 * 应用全局状态对象，集中管理所有可变数据
 * - parsedMap:      解析后的地图数据结构
 * - gridOffscreen:  离屏 Canvas，存储绘制好的占据栅格图像
 * - camera:         相机状态（中心世界坐标 + 缩放级别）
 * - layerVisibility: 各图层的可见性开关
 * - mouseState:     鼠标位置与拖拽状态
 * - hoveredElement: 当前鼠标悬停命中的元素
 * - isMapLoaded:    地图是否已加载完成
 */
const applicationState = {
    parsedMap: null,                                      // 解析后的完整地图数据
    gridOffscreen: null,                                      // 栅格图像（离屏Canvas）
    camera: {                                                     // 相机参数
        centerX: 0,                                                // 相机中心的世界坐标 X
        centerY: 0,                                                // 相机中心的世界坐标 Y
        pixelsPerMeter: 40,                                        // 缩放：每米对应的屏幕像素数
    },
    layerVisibility: {                                            // 各图层是否可见
        grid: true,                                       // 占据栅格底图
        edges: true,                                       // 墙壁边缘发光
        gridlines: true,                                       // 1米网格辅助线
        normalLines: true,                                       // normalLineList
        advLines: true,                                       // advancedLineList
        curves: true,                                       // advancedCurveList
        points: true,                                       // advancedPointList
        areas: true,                                       // advancedAreaList
    },
    mouseState: {                                                 // 鼠标交互状态
        screenX: 0,                                                // 鼠标在 Canvas 上的像素 X
        screenY: 0,                                                // 鼠标在 Canvas 上的像素 Y
        worldX: 0,                                                // 鼠标对应的世界坐标 X
        worldY: 0,                                                // 鼠标对应的世界坐标 Y
        isDragging: false,                                         // 是否正在拖拽平移
    },
    hoveredElement: null,                                         // 当前悬停的元素 {kind, index}
    isMapLoaded: false,                                           // 地图加载完成标志
};

let mapCanvas;     // 主渲染画布 DOM 元素
let canvasCtx;                 // 2D 渲染上下文
let canvasWidthPx = 0;                                        // Canvas CSS 逻辑宽度（像素）
let canvasHeightPx = 0;                                        // Canvas CSS 逻辑高度（像素）
let devicePixelRatio = 1;                                      // 设备像素比（高DPI屏幕 > 1）

const init = () => {
    mapCanvas = document.getElementById('map-canvas');
    canvasCtx = mapCanvas.getContext('2d');
}

/**
 * 根据容器尺寸和设备像素比调整 Canvas 大小
 * 在窗口 resize 时自动调用
 */
function resizeCanvasToContainer() {
    const containerRect = mapCanvas.parentElement.getBoundingClientRect(); // 获取容器实际尺寸
    devicePixelRatio = window.devicePixelRatio || 1;             // 获取设备像素比
    canvasWidthPx = containerRect.width;                        // 更新逻辑宽度
    canvasHeightPx = containerRect.height;                  // 逻辑高度 = 容器高度 - 状态栏高度
    mapCanvas.width = canvasWidthPx * devicePixelRatio;         // Canvas 物理像素宽
    mapCanvas.height = canvasHeightPx * devicePixelRatio;        // Canvas 物理像素高
    mapCanvas.style.width = canvasWidthPx + 'px';               // CSS 显示宽度
    mapCanvas.style.height = canvasHeightPx + 'px';              // CSS 显示高度
}

/**
 * 将世界坐标转换为 Canvas 屏幕像素坐标
 * 注意：世界坐标 Y 轴向上，Canvas Y 轴向下，因此需要翻转
 * @param {number} worldX - 世界坐标 X（米）
 * @param {number} worldY - 世界坐标 Y（米）
 * @returns {{x: number, y: number}} Canvas 上的屏幕像素坐标
 */
function worldToScreen(worldX, worldY) {
    const camera = applicationState.camera;
    const screenX = (worldX - camera.centerX) * camera.pixelsPerMeter + canvasWidthPx / 2;   // 水平偏移 + 居中
    const screenY = -(worldY - camera.centerY) * camera.pixelsPerMeter + canvasHeightPx / 2; // 垂直翻转 + 居中
    return { x: screenX, y: screenY };
}

/**
 * 将 Canvas 屏幕像素坐标反算为世界坐标
 * 与 worldToScreen 互为逆运算
 * @param {number} screenX - Canvas 像素 X
 * @param {number} screenY - Canvas 像素 Y
 * @returns {{x: number, y: number}} 世界坐标（米）
 */
function screenToWorld(screenX, screenY) {
    const camera = applicationState.camera;
    const worldX = (screenX - canvasWidthPx / 2) / camera.pixelsPerMeter + camera.centerX;   // 水平反算
    const worldY = -(screenY - canvasHeightPx / 2) / camera.pixelsPerMeter + camera.centerY;  // 垂直反算（带翻转）
    return { x: worldX, y: worldY };
}

/**
 * 解析 SEER smap JSON 文件为内部地图数据结构
 *
 * @param {Object} rawJson - 原始 JSON 解析后的对象
 * @returns {Object} 解析后的统一地图数据结构
 */
function parseSmapJson(rawJson) {
    // ── 解析 header ──
    const header = rawJson.header || rawJson;                    // 兼容 header 嵌套或平铺
    const minimumPos = header.minPos || { x: 0, y: 0 };         // 地图左下角（世界坐标）
    const maximumPos = header.maxPos || { x: 1, y: 1 };         // 地图右上角（世界坐标）
    const resolution = header.resolution || 0.05;                // 每像素代表的世界长度（米）

    // ── 计算栅格尺寸 ──
    const worldWidth = maximumPos.x - minimumPos.x;            // 地图世界宽度（米）
    const worldHeight = maximumPos.y - minimumPos.y;            // 地图世界高度（米）
    const gridWidthPx = Math.ceil(worldWidth / resolution) + 1; // 栅格像素宽度
    const gridHeightPx = Math.ceil(worldHeight / resolution) + 1;// 栅格像素高度

    // ── 构建统一数据结构 ──
    const parsedMap = {
        name: header.mapName || 'Untitled',             // 地图名称
        version: header.version || '',                      // 版本号
        mapType: header.mapType || '',                      // 地图类型（如 "2D-Map"）
        resolution: resolution,                                // 分辨率（米/像素）
        originX: minimumPos.x,                              // 地图左下角 X
        originY: minimumPos.y,                              // 地图左下角 Y
        maxX: maximumPos.x,                              // 地图右上角 X
        maxY: maximumPos.y,                              // 地图右上角 Y
        gridWidth: gridWidthPx,                               // 栅格宽度（像素）
        gridHeight: gridHeightPx,                              // 栅格高度（像素）

        // normalPosList: 自由空间点，直接拷贝
        normalPositions: rawJson.normalPosList || [],

        // normalLineList: 直接拷贝，每项含 {startPos:{x,y}, endPos:{x,y}}
        normalLines: rawJson.normalLineList || [],

        // advancedPointList: 直接拷贝，每项含 {className, instanceName, pos:{x,y}, dir, desc, property:[]}
        advancedPoints: rawJson.advancedPointList || [],

        // advancedLineList: 直接拷贝，每项含 {className, instanceName, line:{startPos, endPos}}
        advancedLines: rawJson.advancedLineList || [],

        // advancedCurveList: 直接拷贝，每项含 {className, instanceName, controlPos1, controlPos2, startPos, endPos}
        advancedCurves: rawJson.advancedCurveList || [],

        // advancedAreaList: 直接拷贝，每项含 {className, instanceName, dir, posGroup:[], attribute:{}}
        advancedAreas: rawJson.advancedAreaList || [],
    };

    return parsedMap;
}

// ═══════════════════════════════════════════════════════════════════════
// 7. 栅格图像构建器
//    从 normalPosList 反向构建占据栅格并渲染为 Canvas 图像
// ═══════════════════════════════════════════════════════════════════════

/**
 * 从 normalPosList 构建占据栅格图像
 *
 * 原理：normalPosList 中每个点标记一个自由通行格子。
 *       未出现在列表中的格子 = 障碍物（墙壁）
 *       墙壁格子如果紧邻自由格子，则标记为"墙壁边缘"，渲染高亮
 *
 * @param {Object} parsedMap - parseSmapJson 的输出
 * @param {boolean} showEdgeGlow - 是否计算墙壁边缘高亮效果
 * @returns {HTMLCanvasElement} 离屏 Canvas 元素，包含渲染好的栅格图像
 */
function buildOccupancyGridImage(parsedMap, showEdgeGlow) {
    const gridW = parsedMap.gridWidth;                           // 栅格宽度（像素）
    const gridH = parsedMap.gridHeight;                          // 栅格高度（像素）
    const originX = parsedMap.originX;                           // 世界坐标原点 X
    const originY = parsedMap.originY;                           // 世界坐标原点 Y
    const resolution = parsedMap.resolution;                     // 分辨率

    // ── 第一步：遍历 normalPosList，标记自由空间 ──
    const occupancyGrid = new Uint8Array(gridW * gridH);        // 0=墙壁, 1=自由空间
    for (const freeSpacePoint of parsedMap.normalPositions) {
        // 将世界坐标转换为栅格像素坐标
        const gridColX = Math.floor((freeSpacePoint.x - originX) / resolution); // 列号（从左到右）
        const pixelRowFromBottom = Math.floor((freeSpacePoint.y - originY) / resolution); // 行号（从下到上，世界坐标系）
        const imageRowY = gridH - 1 - pixelRowFromBottom;         // 翻转为图像行号（从上到下）
        // 安全边界检查后标记为自由空间
        if (gridColX >= 0 && gridColX < gridW && imageRowY >= 0 && imageRowY < gridH) {
            occupancyGrid[imageRowY * gridW + gridColX] = 1;        // 标记该格为自由
        }
    }

    // ── 第二步：遍历每个格子，按类型着色 ──
    const offscreenCanvas = document.createElement('canvas');    // 创建离屏 Canvas
    offscreenCanvas.width = gridW;                              // 设置尺寸
    offscreenCanvas.height = gridH;
    const offscreenCtx = offscreenCanvas.getContext('2d');        // 获取上下文
    const imageData = offscreenCtx.createImageData(gridW, gridH);// 创建 ImageData
    const pixelData = imageData.data;                            // 像素数据数组 (RGBA 扁平排列)

    // 定义三种颜色
    const FREE_SPACE_R = 10, FREE_SPACE_G = 16, FREE_SPACE_B = 30;    // 自由空间：深蓝黑
    const WALL_INTERIOR_R = 22, WALL_INTERIOR_G = 36, WALL_INTERIOR_B = 64; // 墙壁内部：深蓝
    const WALL_EDGE_R = 45, WALL_EDGE_G = 130, WALL_EDGE_B = 255;     // 墙壁边缘：亮蓝

    for (let rowY = 0; rowY < gridH; rowY++) {                   // 逐行扫描
        for (let colX = 0; colX < gridW; colX++) {                 // 逐列扫描
            const gridIndex = rowY * gridW + colX;                   // 一维数组索引
            const pixelOffset = gridIndex << 2;                      // RGBA 偏移（×4）

            if (occupancyGrid[gridIndex] === 1) {
                // ── 自由空间 ──
                pixelData[pixelOffset] = FREE_SPACE_R;             // R
                pixelData[pixelOffset + 1] = FREE_SPACE_G;             // G
                pixelData[pixelOffset + 2] = FREE_SPACE_B;             // B
                pixelData[pixelOffset + 3] = 255;                      // A（不透明）
            } else {
                // ── 障物（墙壁）── 判断是否为边缘
                let isAdjacentToFreeSpace = false;                     // 是否紧邻自由空间
                if (showEdgeGlow) {                                    // 仅在启用边缘效果时检测
                    if (rowY > 0 && occupancyGrid[(rowY - 1) * gridW + colX] === 1) isAdjacentToFreeSpace = true;    // 上方
                    else if (rowY < gridH - 1 && occupancyGrid[(rowY + 1) * gridW + colX] === 1) isAdjacentToFreeSpace = true; // 下方
                    else if (colX > 0 && occupancyGrid[rowY * gridW + colX - 1] === 1) isAdjacentToFreeSpace = true;   // 左方
                    else if (colX < gridW - 1 && occupancyGrid[rowY * gridW + colX + 1] === 1) isAdjacentToFreeSpace = true;   // 右方
                }
                if (isAdjacentToFreeSpace) {
                    // 墙壁边缘：亮蓝色
                    pixelData[pixelOffset] = WALL_EDGE_R;
                    pixelData[pixelOffset + 1] = WALL_EDGE_G;
                    pixelData[pixelOffset + 2] = WALL_EDGE_B;
                    pixelData[pixelOffset + 3] = 255;
                } else {
                    // 墙壁内部：深蓝色
                    pixelData[pixelOffset] = WALL_INTERIOR_R;
                    pixelData[pixelOffset + 1] = WALL_INTERIOR_G;
                    pixelData[pixelOffset + 2] = WALL_INTERIOR_B;
                    pixelData[pixelOffset + 3] = 255;
                }
            }
        }
    }

    // 将 ImageData 绘制到离屏 Canvas 上
    offscreenCtx.putImageData(imageData, 0, 0);
    return offscreenCanvas;
}


/**
 * 加载地图到应用：构建栅格图像、设置相机、更新UI
 * @param {Object} parsedMap - parseSmapJson 的输出
 */
async function loadMapIntoApplication(parsedMap) {
    await new Promise(resolve => setTimeout(resolve, 30));       // 短暂等待让 UI 有机会刷新

    // 保存地图数据到全局状态
    applicationState.parsedMap = parsedMap;
    // 构建栅格图像（带边缘高亮）
    applicationState.gridOffscreen = buildOccupancyGridImage(parsedMap, applicationState.layerVisibility.edges);

    // ── 设置相机初始位置（地图中心）──
    applicationState.camera.centerX = (parsedMap.originX + parsedMap.maxX) / 2; // 水平居中
    applicationState.camera.centerY = (parsedMap.originY + parsedMap.maxY) / 2; // 垂直居中
    // 计算合适的缩放级别，使地图完整显示在画布内
    const worldWidthMeters = parsedMap.maxX - parsedMap.originX; // 地图世界宽度
    const worldHeightMeters = parsedMap.maxY - parsedMap.originY; // 地图世界高度
    const fitScaleX = canvasWidthPx / worldWidthMeters;           // 水平方向恰好填满的缩放
    const fitScaleY = canvasHeightPx / worldHeightMeters;         // 垂直方向恰好填满的缩放
    applicationState.camera.pixelsPerMeter = Math.max(1, Math.min(fitScaleX, fitScaleY) * 0.88); // 取较小值并留12%边距

    // 标记加载完成并更新UI
    applicationState.isMapLoaded = true;
}

// ═══════════════════════════════════════════════════════════════════════
// 9. 渲染器 — 每帧调用，绘制所有图层
// ═══════════════════════════════════════════════════════════════════════

/** 全局动画时间戳（秒），用于脉冲动画效果 */
let animationTimeSeconds = 0;

/**
 * 主渲染函数，由 requestAnimationFrame 循环调用
 * 按层顺序绘制：栅格 → 网格线 → 区域 → 基础线 → 高级线 → 曲线 → 点 → 边界
 *
 * @param {number} timestamp - requestAnimationFrame 提供的时间戳（毫秒）
 */
function renderFrame(timestamp) {
    // 更新动画时间
    animationTimeSeconds = (timestamp || 0) / 1000;

    // 设置 Canvas 变换矩阵（考虑设备像素比）
    canvasCtx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
    canvasCtx.clearRect(0, 0, canvasWidthPx, canvasHeightPx);    // 清空画布

    // 如果地图未加载，跳过绘制
    if (!applicationState.isMapLoaded) {
        requestAnimationFrame(renderFrame);                         // 继续请求下一帧
        return;
    }

    const map = applicationState.parsedMap;                       // 当前地图数据
    const layers = applicationState.layerVisibility;              // 图层可见性

    // ─────────────────────────────────────────
    // 图层1: 占据栅格底图
    // ─────────────────────────────────────────
    if (layers.grid && applicationState.gridOffscreen) {
        const topLeftScreen = worldToScreen(map.originX, map.maxY);         // 图像左上角（世界坐标→屏幕）
        const bottomRightScreen = worldToScreen(map.maxX, map.originY);     // 图像右下角（世界坐标→屏幕）
        canvasCtx.imageSmoothingEnabled = false;                   // 关闭抗锯齿（保持像素清晰）
        canvasCtx.drawImage(
            applicationState.gridOffscreen,                          // 离屏 Canvas 图像源
            topLeftScreen.x, topLeftScreen.y,                        // 目标左上角
            bottomRightScreen.x - topLeftScreen.x,                   // 目标宽度
            bottomRightScreen.y - topLeftScreen.y                    // 目标高度
        );
    }

    // ─────────────────────────────────────────
    // 图层2: 网格辅助线（每1米一条）
    // ─────────────────────────────────────────
    if (layers.gridlines) {
        // 计算当前可视范围的世界坐标
        const viewTopLeft = screenToWorld(0, 0);               // 视口左上角世界坐标
        const viewBottomRight = screenToWorld(canvasWidthPx, canvasHeightPx); // 视口右下角世界坐标
        const viewMinX = Math.floor(Math.min(viewTopLeft.x, viewBottomRight.x)); // 可视X最小整数
        const viewMaxX = Math.ceil(Math.max(viewTopLeft.x, viewBottomRight.x));  // 可视X最大整数
        const viewMinY = Math.floor(Math.min(viewTopLeft.y, viewBottomRight.y)); // 可视Y最小整数
        const viewMaxY = Math.ceil(Math.max(viewTopLeft.y, viewBottomRight.y));  // 可视Y最大整数

        // 绘制1米间隔的浅色网格线
        canvasCtx.strokeStyle = 'rgba(30,50,80,0.25)';             // 淡蓝灰色
        canvasCtx.lineWidth = 0.5;                                 // 细线
        canvasCtx.beginPath();
        for (let worldX = viewMinX; worldX <= viewMaxX; worldX++) { // 逐列绘制垂直线
            const screenPos = worldToScreen(worldX, 0);
            canvasCtx.moveTo(screenPos.x, 0);                        // 从画布顶部
            canvasCtx.lineTo(screenPos.x, canvasHeightPx);            // 到画布底部
        }
        for (let worldY = viewMinY; worldY <= viewMaxY; worldY++) { // 逐行绘制水平线
            const screenPos = worldToScreen(0, worldY);
            canvasCtx.moveTo(0, screenPos.y);                         // 从画布左边
            canvasCtx.lineTo(canvasWidthPx, screenPos.y);             // 到画布右边
        }
        canvasCtx.stroke();
    }

    // ─────────────────────────────────────────
    // 图层3: advancedAreaList（半透明多边形区域）
    // ─────────────────────────────────────────
    if (layers.areas) {
        for (let areaIndex = 0; areaIndex < map.advancedAreas.length; areaIndex++) {
            const area = map.advancedAreas[areaIndex];                // 当前区域
            const vertices = area.posGroup || [];                     // 多边形顶点数组
            if (vertices.length < 3) continue;                        // 少于3个顶点无法构成多边形，跳过

            // ── 颜色：优先使用 attribute 中的 ARGB 整数，否则用默认品红色 ──
            let fillColorCss = 'rgba(224,64,251,0.12)';            // 默认填充（品红半透明）
            let strokeColorCss = 'rgba(224,64,251,0.5)';             // 默认描边（品红较不透明）
            if (area.attribute) {
                const brushColor = decodeArgbInteger(area.attribute.colorBrush); // 解码填充色
                const penColor = decodeArgbInteger(area.attribute.colorPen);   // 解码描边色
                if (brushColor) {
                    // 使用原始 alpha 值的一半作为填充透明度，最小 0.06
                    fillColorCss = toCssRgba(brushColor, Math.max(0.06, (brushColor.a / 255) * 0.5));
                }
                if (penColor) {
                    strokeColorCss = toCssRgba(penColor, 0.6);           // 描边60%不透明
                }
            }

            // ── 绘制多边形 ──
            canvasCtx.fillStyle = fillColorCss;                     // 设置填充色
            canvasCtx.strokeStyle = strokeColorCss;                   // 设置描边色
            canvasCtx.lineWidth = 1.2;                              // 描边宽度
            canvasCtx.beginPath();
            const firstVertexScreen = worldToScreen(vertices[0].x, vertices[0].y); // 第一个顶点的屏幕位置
            canvasCtx.moveTo(firstVertexScreen.x, firstVertexScreen.y); // 移动到起点
            for (let i = 1; i < vertices.length; i++) {              // 依次连接后续顶点
                const vertexScreen = worldToScreen(vertices[i].x, vertices[i].y);
                canvasCtx.lineTo(vertexScreen.x, vertexScreen.y);
            }
            canvasCtx.closePath();                                    // 闭合路径
            canvasCtx.fill();                                         // 填充
            canvasCtx.stroke();                                       // 描边

            // ── 在多边形质心处绘制区域名称标签 ──
            let centroidWorldX = 0, centroidWorldY = 0;              // 质心世界坐标
            for (const v of vertices) { centroidWorldX += v.x; centroidWorldY += v.y; } // 累加顶点坐标
            centroidWorldX /= vertices.length;                        // 求平均得到质心 X
            centroidWorldY /= vertices.length;                        // 求平均得到质心 Y
            const centroidScreen = worldToScreen(centroidWorldX, centroidWorldY);
            canvasCtx.font = '500 8px "JetBrains Mono"';             // 标签字体
            canvasCtx.fillStyle = strokeColorCss;                     // 与描边同色
            canvasCtx.textAlign = 'center';                           // 居中对齐
            canvasCtx.fillText(area.instanceName || '', centroidScreen.x, centroidScreen.y + 3); // 绘制文字
        }
    }

    // ─────────────────────────────────────────
    // 图层4: normalLineList（基础直线，青色 #00e5ff）
    // ─────────────────────────────────────────
    if (layers.normalLines) {
        for (let lineIndex = 0; lineIndex < map.normalLines.length; lineIndex++) {
            const line = map.normalLines[lineIndex];                  // 当前直线
            const startScreen = worldToScreen(line.startPos.x, line.startPos.y); // 起点屏幕位置
            const endScreen = worldToScreen(line.endPos.x, line.endPos.y);     // 终点屏幕位置

            // 绘制发光直线
            canvasCtx.save();
            canvasCtx.shadowColor = '#00e5ff';                        // 阴影颜色（产生辉光）
            canvasCtx.shadowBlur = 5;                                // 辉光扩散半径
            canvasCtx.strokeStyle = '#00e5ff';                        // 线条颜色：青色
            canvasCtx.lineWidth = 2;                                // 线宽
            canvasCtx.globalAlpha = 0.8;                              // 80%不透明
            canvasCtx.beginPath();
            canvasCtx.moveTo(startScreen.x, startScreen.y);           // 从起点
            canvasCtx.lineTo(endScreen.x, endScreen.y);               // 到终点
            canvasCtx.stroke();
            canvasCtx.restore();

            // 在中点绘制方向箭头
            const midpointX = (startScreen.x + endScreen.x) / 2;     // 中点X
            const midpointY = (startScreen.y + endScreen.y) / 2;     // 中点Y
            const arrowAngle = Math.atan2(endScreen.y - startScreen.y, endScreen.x - startScreen.x); // 线段方向角
            const arrowLength = 5;                                    // 箭头长度
            canvasCtx.fillStyle = 'rgba(0,229,255,0.6)';              // 箭头填充色
            canvasCtx.beginPath();
            canvasCtx.moveTo(midpointX + Math.cos(arrowAngle) * arrowLength, midpointY + Math.sin(arrowAngle) * arrowLength); // 箭头尖端
            canvasCtx.lineTo(midpointX + Math.cos(arrowAngle + 2.5) * 4, midpointY + Math.sin(arrowAngle + 2.5) * 4);       // 左翼
            canvasCtx.lineTo(midpointX + Math.cos(arrowAngle - 2.5) * 4, midpointY + Math.sin(arrowAngle - 2.5) * 4);       // 右翼
            canvasCtx.closePath();
            canvasCtx.fill();
        }
    }

    // ─────────────────────────────────────────
    // 图层5: advancedLineList（高级线，如 ForbiddenLine）
    // ─────────────────────────────────────────
    if (layers.advLines) {
        for (let advLineIndex = 0; advLineIndex < map.advancedLines.length; advLineIndex++) {
            const advLine = map.advancedLines[advLineIndex];          // 当前高级线
            const lineData = advLine.line;                            // 内部的 line 对象
            if (!lineData || !lineData.startPos || !lineData.endPos) continue; // 缺少端点则跳过

            const startScreen = worldToScreen(lineData.startPos.x, lineData.startPos.y);
            const endScreen = worldToScreen(lineData.endPos.x, lineData.endPos.y);

            // ── 根据 className 决定颜色和线型 ──
            let lineColorHex = '#ff6e40';                             // 默认橙色
            let lineDashPattern = [];                                 // 默认实线（无虚线）
            const classNameLower = (advLine.className || '').toLowerCase();
            if (classNameLower.includes('forbidden')) {               // 禁行线 → 红色虚线
                lineColorHex = '#ff1744';
                lineDashPattern = [8, 4];                               // 虚线模式：8px实 4px空
            } else if (classNameLower.includes('virtual')) {          // 虚拟线 → 橙色虚线
                lineColorHex = '#ffab40';
                lineDashPattern = [4, 4];
            }

            // 绘制高级线
            canvasCtx.save();
            canvasCtx.shadowColor = lineColorHex;                     // 辉光色与线条同色
            canvasCtx.shadowBlur = 4;                                // 辉光半径
            canvasCtx.strokeStyle = lineColorHex;                     // 线条颜色
            canvasCtx.lineWidth = 2.5;                              // 线宽（比基础线粗）
            canvasCtx.setLineDash(lineDashPattern);                   // 设置虚线模式
            canvasCtx.globalAlpha = 0.8;                              // 80%不透明
            canvasCtx.beginPath();
            canvasCtx.moveTo(startScreen.x, startScreen.y);
            canvasCtx.lineTo(endScreen.x, endScreen.y);
            canvasCtx.stroke();
            canvasCtx.setLineDash([]);                                // 恢复实线
            canvasCtx.restore();

            // 在中点绘制名称标签
            const midpointScreenX = (startScreen.x + endScreen.x) / 2;
            const midpointScreenY = (startScreen.y + endScreen.y) / 2;
            canvasCtx.font = '500 8px "JetBrains Mono"';
            canvasCtx.fillStyle = hexToCssRgba(lineColorHex, 0.55);   // 55%不透明的文字
            canvasCtx.textAlign = 'center';
            canvasCtx.fillText(advLine.instanceName || advLine.className || '', midpointScreenX, midpointScreenY - 6);
        }
    }

    // ─────────────────────────────────────────
    // 图层6: advancedCurveList（贝塞尔曲线路径，酸绿色 #76ff03）
    // ─────────────────────────────────────────
    if (layers.curves) {
        for (let curveIndex = 0; curveIndex < map.advancedCurves.length; curveIndex++) {
            const curve = map.advancedCurves[curveIndex];             // 当前曲线

            // 提取起点和终点的 pos（注意：startPos/endPos 是 {instanceName, pos:{x,y}} 结构）
            const startPoint = curve.startPos?.pos;                   // 起点世界坐标
            const endPoint = curve.endPos?.pos;                     // 终点世界坐标
            const controlPoint1 = curve.controlPos1;                  // 第一控制点（直接 {x,y}）
            const controlPoint2 = curve.controlPos2;                  // 第二控制点（直接 {x,y}）
            if (!startPoint || !endPoint) continue;                   // 缺少端点则跳过

            // 转换为屏幕坐标
            const startScreen = worldToScreen(startPoint.x, startPoint.y);
            const endScreen = worldToScreen(endPoint.x, endPoint.y);
            const cp1Screen = controlPoint1 ? worldToScreen(controlPoint1.x, controlPoint1.y) : null; // 可能不存在
            const cp2Screen = controlPoint2 ? worldToScreen(controlPoint2.x, controlPoint2.y) : null;

            // ── 绘制贝塞尔曲线（虚线）──
            canvasCtx.save();
            canvasCtx.shadowColor = '#76ff03';                        // 酸绿色辉光
            canvasCtx.shadowBlur = 5;
            canvasCtx.strokeStyle = '#76ff03';                        // 酸绿色线条
            canvasCtx.lineWidth = 2;
            canvasCtx.setLineDash([8, 5]);                            // 虚线模式
            canvasCtx.globalAlpha = 0.85;
            canvasCtx.beginPath();
            canvasCtx.moveTo(startScreen.x, startScreen.y);           // 起点
            if (cp1Screen && cp2Screen) {
                // 有2个控制点 → 三次贝塞尔 (cubic bezier)
                canvasCtx.bezierCurveTo(cp1Screen.x, cp1Screen.y, cp2Screen.x, cp2Screen.y, endScreen.x, endScreen.y);
            } else if (cp1Screen) {
                // 仅1个控制点 → 二次贝塞尔 (quadratic bezier)
                canvasCtx.quadraticCurveTo(cp1Screen.x, cp1Screen.y, endScreen.x, endScreen.y);
            } else {
                // 无控制点 → 直线
                canvasCtx.lineTo(endScreen.x, endScreen.y);
            }
            canvasCtx.stroke();
            canvasCtx.setLineDash([]);                                // 恢复实线
            canvasCtx.restore();

            // ── 在 t=0.5 处绘制方向箭头 ──
            if (cp1Screen && cp2Screen) {
                const t = 0.5;                                          // 参数 t = 0.5（曲线中点）
                const oneMinusT = 0.5;                                  // 1-t = 0.5
                // 三次贝塞尔公式计算中点坐标
                const arrowX = oneMinusT ** 3 * startScreen.x + 3 * oneMinusT ** 2 * t * cp1Screen.x + 3 * oneMinusT * t ** 2 * cp2Screen.x + t ** 3 * endScreen.x;
                const arrowY = oneMinusT ** 3 * startScreen.y + 3 * oneMinusT ** 2 * t * cp1Screen.y + 3 * oneMinusT * t ** 2 * cp2Screen.y + t ** 3 * endScreen.y;
                // 三次贝塞尔的一阶导数（切线方向）
                const tangentX = 3 * oneMinusT ** 2 * (cp1Screen.x - startScreen.x) + 6 * oneMinusT * t * (cp2Screen.x - cp1Screen.x) + 3 * t ** 2 * (endScreen.x - cp2Screen.x);
                const tangentY = 3 * oneMinusT ** 2 * (cp1Screen.y - startScreen.y) + 6 * oneMinusT * t * (cp2Screen.y - cp1Screen.y) + 3 * t ** 2 * (endScreen.y - cp2Screen.y);
                const tangentAngle = Math.atan2(tangentY, tangentX);     // 切线角度

                // 绘制箭头
                canvasCtx.fillStyle = 'rgba(118,255,3,0.7)';
                canvasCtx.beginPath();
                canvasCtx.moveTo(arrowX + Math.cos(tangentAngle) * 5, arrowY + Math.sin(tangentAngle) * 5);
                canvasCtx.lineTo(arrowX + Math.cos(tangentAngle + 2.5) * 4, arrowY + Math.sin(tangentAngle + 2.5) * 4);
                canvasCtx.lineTo(arrowX + Math.cos(tangentAngle - 2.5) * 4, arrowY + Math.sin(tangentAngle - 2.5) * 4);
                canvasCtx.closePath();
                canvasCtx.fill();

                // 绘制控制点小圆（辅助调试用）
                canvasCtx.fillStyle = 'rgba(118,255,3,0.25)';           // 浅绿色半透明
                for (const controlScreen of [cp1Screen, cp2Screen]) {
                    canvasCtx.beginPath();
                    canvasCtx.arc(controlScreen.x, controlScreen.y, 2.5, 0, Math.PI * 2); // 半径2.5px的小圆
                    canvasCtx.fill();
                }
            }

            // ── 曲线名称标签 ──
            canvasCtx.font = '500 7px "JetBrains Mono"';
            canvasCtx.fillStyle = 'rgba(118,255,3,0.5)';
            canvasCtx.textAlign = 'center';
            const labelScreenY = Math.min(startScreen.y, endScreen.y) - 8; // 标签放在最高点上方
            canvasCtx.fillText(curve.instanceName || '', (startScreen.x + endScreen.x) / 2, labelScreenY);
        }
    }

    // ─────────────────────────────────────────
    // 图层7: advancedPointList（高级点标记，菱形）
    // ─────────────────────────────────────────
    if (layers.points) {
        for (let pointIndex = 0; pointIndex < map.advancedPoints.length; pointIndex++) {
            const advPoint = map.advancedPoints[pointIndex];          // 当前高级点
            const pointScreen = worldToScreen(advPoint.pos.x, advPoint.pos.y); // 屏幕位置

            // 判断是否被鼠标悬停
            const isHovered = applicationState.hoveredElement &&
                applicationState.hoveredElement.kind === 'point' &&
                applicationState.hoveredElement.index === pointIndex;

            // ── 根据 className 决定颜色 ──
            let pointColorHex = '#ffd600';                            // 默认琥珀色
            const classNameLower = (advPoint.className || '').toLowerCase();
            if (classNameLower.includes('landmark') || classNameLower.includes('land')) {
                pointColorHex = '#ffd600';                              // LandMark → 琥珀色
            } else if (classNameLower.includes('charge')) {
                pointColorHex = '#22c55e';                              // 充电点 → 绿色
            } else if (classNameLower.includes('load')) {
                pointColorHex = '#3b82f6';                              // 装载点 → 蓝色
            } else if (classNameLower.includes('work') || classNameLower.includes('process')) {
                pointColorHex = '#a855f7';                              // 工位 → 紫色
            } else if (classNameLower.includes('station')) {
                pointColorHex = '#00e5ff';                              // 站点 → 青色
            }

            // ── 绘制菱形标记 ──
            const diamondRadius = isHovered ? 8 : 5;                 // 悬停时放大
            const pulseAlpha = isHovered ? Math.sin(animationTimeSeconds * 5) * 0.2 + 0.8 : 1; // 悬停时脉冲闪烁

            canvasCtx.save();
            canvasCtx.globalAlpha = pulseAlpha;                       // 应用透明度
            canvasCtx.shadowColor = pointColorHex;                    // 辉光颜色
            canvasCtx.shadowBlur = isHovered ? 18 : 8;              // 悬停时增强辉光

            // 绘制旋转45度的正方形（菱形）
            canvasCtx.fillStyle = pointColorHex;
            canvasCtx.beginPath();
            canvasCtx.moveTo(pointScreen.x, pointScreen.y - diamondRadius);          // 上顶点
            canvasCtx.lineTo(pointScreen.x + diamondRadius * 0.7, pointScreen.y);    // 右顶点
            canvasCtx.lineTo(pointScreen.x, pointScreen.y + diamondRadius);          // 下顶点
            canvasCtx.lineTo(pointScreen.x - diamondRadius * 0.7, pointScreen.y);    // 左顶点
            canvasCtx.closePath();
            canvasCtx.fill();

            // 中心白色小圆点（指示精确位置）
            canvasCtx.shadowBlur = 0;
            canvasCtx.fillStyle = '#fff';
            canvasCtx.globalAlpha = 0.9;
            canvasCtx.beginPath();
            canvasCtx.arc(pointScreen.x, pointScreen.y, 1.8, 0, Math.PI * 2); // 半径1.8px
            canvasCtx.fill();
            canvasCtx.restore();

            // ── 方向箭头（基于 dir 字段，弧度）──
            if (advPoint.dir !== undefined && advPoint.dir !== null) {
                // dir 可能是字符串（smap 样例中为 "-1.5759999999999958"）或数字
                const dirRadians = typeof advPoint.dir === 'string' ? parseFloat(advPoint.dir) : advPoint.dir;
                if (!isNaN(dirRadians)) {                               // 有效方向角
                    const arrowLengthPx = 12;                            // 箭头长度（屏幕像素）
                    canvasCtx.strokeStyle = hexToCssRgba(pointColorHex, 0.5); // 半透明的线条
                    canvasCtx.lineWidth = 1;
                    canvasCtx.beginPath();
                    canvasCtx.moveTo(pointScreen.x, pointScreen.y);       // 从点中心出发
                    // 注意：世界坐标 Y 向上，但 atan2 使用的 dir 是标准数学角，屏幕 Y 翻转后取负
                    canvasCtx.lineTo(
                        pointScreen.x + Math.cos(dirRadians) * arrowLengthPx,
                        pointScreen.y - Math.sin(dirRadians) * arrowLengthPx // Y 翻转
                    );
                    canvasCtx.stroke();
                }
            }

            // ── 名称标签 ──
            canvasCtx.font = '500 7px "JetBrains Mono"';
            canvasCtx.fillStyle = hexToCssRgba(pointColorHex, 0.7);
            canvasCtx.textAlign = 'center';
            canvasCtx.fillText(advPoint.instanceName || '', pointScreen.x, pointScreen.y - diamondRadius - 4);
        }
    }

    // ─────────────────────────────────────────
    // 地图边界虚线框
    // ─────────────────────────────────────────
    const boundaryTopLeft = worldToScreen(map.originX, map.maxY);   // 边界左上角屏幕位置
    const boundaryBottomRight = worldToScreen(map.maxX, map.originY);   // 边界右下角屏幕位置
    canvasCtx.strokeStyle = 'rgba(0,229,160,0.1)';                      // 极淡的青绿色
    canvasCtx.lineWidth = 1;
    canvasCtx.setLineDash([5, 4]);                                      // 虚线
    canvasCtx.strokeRect(
        boundaryTopLeft.x, boundaryTopLeft.y,
        boundaryBottomRight.x - boundaryTopLeft.x,
        boundaryBottomRight.y - boundaryTopLeft.y
    );
    canvasCtx.setLineDash([]);                                          // 恢复实线

    // 请求下一帧渲染
    requestAnimationFrame(renderFrame);
}

const bindEvent = () => {
    // ── 鼠标移动：更新坐标、拖拽平移、悬停检测 ──
    mapCanvas.addEventListener('mousemove', (mouseEvent) => {
        const canvasRect = mapCanvas.getBoundingClientRect();        // Canvas 在页面中的位置
        applicationState.mouseState.screenX = mouseEvent.clientX - canvasRect.left; // 鼠标在 Canvas 上的 X
        applicationState.mouseState.screenY = mouseEvent.clientY - canvasRect.top;  // 鼠标在 Canvas 上的 Y
        // 反算世界坐标
        const worldPos = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY);
        applicationState.mouseState.worldX = worldPos.x;
        applicationState.mouseState.worldY = worldPos.y;

        // 如果正在拖拽，平移相机
        if (applicationState.mouseState.isDragging) {
            applicationState.camera.centerX -= mouseEvent.movementX / applicationState.camera.pixelsPerMeter; // 水平平移
            applicationState.camera.centerY += mouseEvent.movementY / applicationState.camera.pixelsPerMeter; // 垂直平移（Y翻转）
        }
    });

    // ── 鼠标按下：开始拖拽 ──
    mapCanvas.addEventListener('mousedown', () => {
        applicationState.mouseState.isDragging = true;               // 标记拖拽开始
    });

    // ── 鼠标抬起：结束拖拽 ──
    mapCanvas.addEventListener('mouseup', () => {
        applicationState.mouseState.isDragging = false;              // 标记拖拽结束
    });

    // ── 鼠标离开画布：结束拖拽并隐藏 Tooltip ──
    mapCanvas.addEventListener('mouseleave', () => {
        applicationState.mouseState.isDragging = false;
    });

    // ── 滚轮：缩放（向鼠标位置缩放）──
    mapCanvas.addEventListener('wheel', (wheelEvent) => {
        wheelEvent.preventDefault();                                  // 阻止页面滚动
        const zoomFactor = wheelEvent.deltaY > 0 ? 0.88 : 1.12;     // 向下滚动=缩小，向上滚动=放大
        const mouseWorldBefore = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY); // 缩放前鼠标世界坐标
        applicationState.camera.pixelsPerMeter = Math.max(0.5, Math.min(2000, applicationState.camera.pixelsPerMeter * zoomFactor)); // 应用缩放并钳制范围
        const mouseWorldAfter = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY);   // 缩放后鼠标世界坐标
        // 调整相机中心，使鼠标指向的世界坐标保持不变
        applicationState.camera.centerX += mouseWorldBefore.x - mouseWorldAfter.x;
        applicationState.camera.centerY += mouseWorldBefore.y - mouseWorldAfter.y;
    }, { passive: false });                                          // passive:false 允许 preventDefault
}

const loadSourceMap = async (url) => {
    const response = await fetch(url);
    if (!response.ok) throw new Error(`加载失败：${response.status}`);
    return response.text();
};

/**
 * 加载 smap JSON 文件
 */
async function loadUserFile() {
    try {
        const fileTextContent = await loadSourceMap('/2.smap');
        const rawJsonObject = JSON.parse(fileTextContent);        // 解析 JSON
        const parsedMap = parseSmapJson(rawJsonObject);        // 转换为内部结构
        await loadMapIntoApplication(parsedMap);                     // 加载到应用
    } catch (parseError) {
        alert('Parse error: ' + parseError.message);                 // 显示错误信息
    }
}

onMounted(() => {
    init()
    // 启动渲染循环
    loadUserFile()
    requestAnimationFrame(renderFrame);
    window.addEventListener('resize', resizeCanvasToContainer);    // 监听窗口大小变化
    resizeCanvasToContainer();                                     // 初始调用一次
    setTimeout(() => {
        bindEvent()
    }, 100)
})

onUnmounted(() => {
    window.removeEventListener('resize', resizeCanvasToContainer)
    canvasCtx = null
})
</script>

<style lang="scss" scoped>
.container {
    width: 100%;
    height: 100%;
}
</style>