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

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

import {
    worldToScreen,
    screenToWorld,
    drawGrid,
    drawGridlines,
    drawAdvancedAreaList,
    drawAdvancedLineList,
    drawNormalLines,
    drawAdvancedCurveList,
    drawAdvancedPointList,
    drawMapBoundary,
    buildOccupancyGridImage,
    MobileRobot
} from './canvasUtils';
import { getCurrentMap, getRobotState } from '@/api/agv.js'
import { ElMessage } from 'element-plus';
import { useRobotStore } from '@/stores/robot'

const props = defineProps({
    mapName: {
        type: String,
        default: ''
    },
    isDragging: {
        type: Boolean,
        default: true
    }
})

const VITE_DEVICE_ID = import.meta.env.VITE_DEVICE_ID

const robotStore = useRobotStore()

/**
 * 应用全局状态对象，集中管理所有可变数据
 * - parsedMap:      解析后的地图数据结构
 * - layerVisibility: 各图层的可见性开关
 * - mouseState:     鼠标位置与拖拽状态
 * - isMapLoaded:    地图是否已加载完成
 */
const applicationState = {
  parsedMap: null,                                      // 解析后的完整地图数据
  gridOffscreen: null,                                      // 栅格图像（离屏Canvas）
  hoveredElement: null,                                     // 当前悬停的元素 {kind, index}
  animationTimeSeconds: 0,                                  // 全局动画时间戳（秒），用于脉冲动画效果
  camera: {                                                 // 相机参数
    centerX: 0,                                                // 相机中心的世界坐标 X
    centerY: 0,                                                // 相机中心的世界坐标 Y
    pixelsPerMeter: 20,                                        // 缩放：每米对应的屏幕像素数
  },
  canvasWidthPx: 0,                                         // Canvas CSS 逻辑宽度（像素）
  canvasHeightPx: 0,                                        // Canvas CSS 逻辑高度（像素）
  devicePixelRatio: 1,                                      // 设备像素比（高DPI屏幕 > 1）
  mouseState: {                                                 // 鼠标交互状态
    screenX: 0,                                                // 鼠标在 Canvas 上的像素 X
    screenY: 0,                                                // 鼠标在 Canvas 上的像素 Y
    worldX: 0,                                                // 鼠标对应的世界坐标 X
    worldY: 0,                                                // 鼠标对应的世界坐标 Y
    isDragging: false,                                         // 是否正在拖拽平移
  },
  isMapLoaded: false,                                           // 地图加载完成标志,
  layerVisibility: {                                            // 各图层是否可见
    grid: true,                                       // 占据栅格底图
    edges: true,                                       // 墙壁边缘发光
    gridlines: true,                                       // 1米网格辅助线
    normalLines: true,                                       // normalLineList
    advLines: true,                                       // advancedLineList
    curves: true,                                       // advancedCurveList
    points: true,                                       // advancedPointList
    areas: true,                                       // advancedAreaList,
    robots: true
  },
  robots: []      // 存储机器人实例
};

let mapCanvas;     // 主渲染画布 DOM 元素
let canvasCtx;                 // 2D 渲染上下文

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

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


/**
 * 解析 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;
}

/**
 * 加载地图到应用：构建栅格图像、设置相机、更新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);

    // ── 设置相机初始位置（地图中心）──
    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 = applicationState.canvasWidthPx / worldWidthMeters;           // 水平方向恰好填满的缩放
    const fitScaleY = applicationState.canvasHeightPx / worldHeightMeters;         // 垂直方向恰好填满的缩放
    applicationState.camera.pixelsPerMeter = Math.max(1, Math.max(fitScaleX, fitScaleY) * 1); // 取较小值

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


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

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

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

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

    // 图层1: 占据栅格底图
    if (layers.grid && applicationState.gridOffscreen) {
        drawGrid(canvasCtx, map, applicationState);
    }

    // 图层2: 网格辅助线（每1米一条）
    if (layers.gridlines) {
        drawGridlines(canvasCtx, applicationState)
    }

    // 图层3: advancedAreaList（半透明多边形区域）
    if (layers.areas) {
        drawAdvancedAreaList(canvasCtx, map, applicationState)
    }

    // 图层4: normalLineList（基础直线，青色 #00e5ff）
    if (layers.normalLines) {
        drawNormalLines(canvasCtx, map, applicationState);
    }

    // 图层5: advancedLineList（高级线，如 ForbiddenLine）
    if (layers.advLines) {
        drawAdvancedLineList(canvasCtx, map, applicationState);
    }

    // 图层6: advancedCurveList（贝塞尔曲线路径，酸绿色 #76ff03）
    if (layers.curves) {
        drawAdvancedCurveList(canvasCtx, map, applicationState);
    }

    // 图层7: advancedPointList（高级点标记，菱形）
    if (layers.points) {
        drawAdvancedPointList(canvasCtx, map, applicationState);
    }

    if (layers.robots) {
        drawAllRobots(canvasCtx)
    }

    // 地图边界虚线框
    drawMapBoundary(canvasCtx, map, applicationState);

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

const drawAllRobots = (ctx) => {
    if (!applicationState.layerVisibility.robots) return;
    for (const robot of applicationState.robots) {
        const screenPos = worldToScreen(robot.x, robot.y, applicationState);
        // 裁剪超出画布简易跳过
        if (screenPos.x + 20 < 0 || screenPos.x - 20 > applicationState.canvasWidthPx || screenPos.y + 20 < 0 || screenPos.y - 20 > applicationState.canvasHeightPx) {
            continue;
        }
        robot.draw(ctx, screenPos.x, screenPos.y);
    }
}

const bindEvent = () => {
    // ── 鼠标移动：更新坐标、拖拽平移、悬停检测 ──
    mapCanvas.addEventListener('mousemove', (mouseEvent) => {
        // console.log('mouseEvent.clientX', mouseEvent.clientX)
        const canvasRect = mapCanvas.getBoundingClientRect();        // Canvas 在页面中的位置
        // console.log('canvasRect.left', canvasRect.left)
        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);
        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翻转）
        }

        let hit = hitTestRobots(worldPos.x, worldPos.y, applicationState);
        updateTooltipDisplay(hit);
    });

    mapCanvas.addEventListener('click', (mouseEvent) => {
        const canvasRect = mapCanvas.getBoundingClientRect(); // Canvas 在页面中的位置
        const screenX = mouseEvent.clientX - canvasRect.left; // 鼠标在 Canvas 上的 X
        const screenY = mouseEvent.clientY - canvasRect.top;  // 鼠标在 Canvas 上的 Y
        const worldPos = screenToWorld(screenX, screenY, applicationState);
    })

    // ── 鼠标按下：开始拖拽 ──
    mapCanvas.addEventListener('mousedown', () => {
        if (!props.isDragging) {
            applicationState.mouseState.isDragging = false;
            return
        }
        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); // 缩放前鼠标世界坐标
        applicationState.camera.pixelsPerMeter = Math.max(0.5, Math.min(2000, applicationState.camera.pixelsPerMeter * zoomFactor)); // 应用缩放并钳制范围
        const mouseWorldAfter = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY, applicationState);   // 缩放后鼠标世界坐标
        // 调整相机中心，使鼠标指向的世界坐标保持不变
        applicationState.camera.centerX += mouseWorldBefore.x - mouseWorldAfter.x;
        applicationState.camera.centerY += mouseWorldBefore.y - mouseWorldAfter.y;
    }, { passive: false });                                          // passive:false 允许 preventDefault
}


const updateTooltipDisplay = (hitResult) => {
    const tooltipEl = document.getElementById('tooltip');
    if (!hitResult) {
        tooltipEl.style.display = 'none';
        applicationState.hoveredElement = null;
        return;
    }
    applicationState.hoveredElement = hitResult;
    let title = '', details = '';
    if (hitResult.kind === 'robot') {
        const robot = hitResult.robot;
        title = `${robot.name}`;
        details = `<div class="tooltip-detail">位置: (${robot.x.toFixed(3)}, ${robot.y.toFixed(3)})</div>
                    <div class="tooltip-detail">朝向: ${(robot.angle * 180 / Math.PI).toFixed(1)}°</div>
                    <div class="tooltip-detail">ID: ${robot.id}</div>`;
    }
    tooltipEl.innerHTML = `<div class="tooltip-title" style="color:#00e5a0">${title}</div>${details}`;
    tooltipEl.style.display = 'block';
    tooltipEl.style.left = Math.min(applicationState.mouseState.screenX + 14, applicationState.canvasWidthPx - 220) + 'px';
    tooltipEl.style.top = Math.max(4, applicationState.mouseState.screenY - 8) + 'px';
}

const hitTestRobots = (worldX, worldY) => {
    for (let i = 0; i < applicationState.robots.length; i++) {
        const robot = applicationState.robots[i];
        if (robot.hitTest(worldX, worldY)) {
            return { kind: 'robot', index: i, robot: robot };
        }
    }
    return null;
}

const zoomCanvas = (zoomIn) => {
    const zoomFactor = zoomIn ? 1.12 : 0.88;
    const worldBefore = screenToWorld(applicationState.canvasWidthPx / 2, applicationState.canvasHeightPx / 2, applicationState);
    applicationState.camera.pixelsPerMeter = Math.max(0.5, Math.min(2000, applicationState.camera.pixelsPerMeter * zoomFactor));
    const worldAfter = screenToWorld(applicationState.canvasWidthPx / 2, applicationState.canvasHeightPx / 2, applicationState);
    applicationState.camera.centerX += worldBefore.x - worldAfter.x;
    applicationState.camera.centerY += worldBefore.y - worldAfter.y;
}

const centerCanvasView = () => {
    const map = applicationState.parsedMap;
    if (!map) return;
    applicationState.camera.centerX = (map.originX + map.maxX) / 2;
    applicationState.camera.centerY = (map.originY + map.maxY) / 2;
}

const loadSourceMap = async (mapName) => {
    const res = await getCurrentMap({
        ip: robotStore.ip,
        deviceId: VITE_DEVICE_ID, 
        mapName: mapName
    })
    if (res.code === 200) {
        try {
            const rawJsonObject = JSON.parse(res.data.map_content);        // 解析 JSON
            const parsedMap = parseSmapJson(rawJsonObject);        // 转换为内部结构
            await loadMapIntoApplication(parsedMap);                     // 加载到应用
            setTimeout(() => {
                bindEvent()
                getRobot()
            }, 300)
        } catch (parseError) {
            ElMessage.error('Parse error: ' + parseError.message);                 // 显示错误信息
        }
    }
};

/**
 * 
 * @param id 机器人编号
 * @param name 机器人名称
 * @param angleRad 角度
 * @param color 颜色
 */
const addRobot = (id, name,x, y, angleRad, color) => {
    const robot = new MobileRobot(id, name, x, y, angleRad, color);
    applicationState.robots.push(robot);
}

const moveRobot = (robot, dx, dy, dtheta = 0) => {
    robot.x += dx;
    robot.y += dy;
    robot.angle += dtheta;
}

const getRobot = async () => {
    const res = await getRobotState({
        ip: robotStore.ip,
        deviceId: VITE_DEVICE_ID, 
    })
    if (res.code === 200) {
        robotStore.setPosition(res.data.x, res.data.y, res.data.angle)
        robotStore.battery = res.data.batteryLevel
        addRobot(robotStore.ip, robotStore.ip, res.data.x, res.data.y, res.data.angle, '#00D4FF')
    }
}

onMounted(() => {
    init()
})

watch(() => props.mapName,
    (newVal) => {
        if (props.mapName) {
            loadSourceMap(props.mapName)
            requestAnimationFrame(renderFrame);
            window.addEventListener('resize', resizeCanvasToContainer);    // 监听窗口大小变化
            resizeCanvasToContainer();                                     // 初始调用一次
        }
    }
)

defineExpose({
    zoomCanvas,
    centerCanvasView
})

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

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

    #tooltip {
        position: absolute;
        display: none;
        background: #0b1120;
        border: 1px solid #2a3a5c;
        padding: 8px 12px;
        border-radius: 3px;
        pointer-events: none;
        z-index: 20;
        font-size: 10px;
        min-width: 180px;
        max-width: 280px;
        box-shadow: 0 4px 16px rgba(0, 0, 0, .5);

        .tooltip-title {
            font-weight: 700;
            font-size: 11px;
            margin-bottom: 5px;
        }

        :deep(.tooltip-detail) {
            color: #94a3b8;
            padding: 2px 0;
            font-size: 9px;
            word-break: break-all;
        }
    }
}
</style>