<script setup lang="ts">
import { DOOR_AREA_TYPE } from '@api/map/door-area';
import { LockState } from '@meta2d/core';
import { message } from 'ant-design-vue';
import dayjs, { type Dayjs } from 'dayjs';
import { isNil } from 'lodash-es';
import { computed, onMounted, onUnmounted, provide, ref, type ShallowRef, shallowRef, watch } from 'vue';
import { useRoute } from 'vue-router';

import type { RobotRealtimeInfo } from '../apis/robot';
import { getSceneByGroupId, getSceneById, monitorRealSceneById, monitorSceneById } from '../apis/scene';
import expandIcon from '../assets/icons/png/expand.png';
import foldIcon from '../assets/icons/png/fold.png';
import foldIcon2 from '../assets/icons/png/fold-2.png';
import FollowViewNotification from '../components/follow-view-notification.vue';
import PlaybackController from '../components/PlaybackController.vue';
import RobotLabels from '../components/robot-labels.vue';
import { usePlaybackWebSocket } from '../hooks/usePlaybackWebSocket';
import { autoDoorSimulationService, type AutoDoorWebSocketData } from '../services/auto-door-simulation.service';
import {
  type ContextMenuState,
  createContextMenuManager,
  handleContextMenuFromPenData,
} from '../services/context-menu.service';
import { EditorService } from '../services/editor.service';
import { StorageLocationService } from '../services/storage-location.service';
import { useViewState } from '../services/useViewState';
import { editorStore } from '../stores/editor.store';

const EDITOR_KEY = Symbol('editor-key');

type Props = {
  sid: string;
  id?: string;
};
const props = defineProps<Props>();

//#region 响应式状态定义
// 获取路由信息以判断当前模式
const route = useRoute();

// 适配多种后端数据格式的解析（参考 scene-editor.vue）
const normalizeSceneJson = (raw: unknown): unknown => {
  if (raw == null) return raw;
  let result: unknown = raw;
  const maxDepth = 2;
  let depth = 0;
  while (typeof result === 'string' && depth < maxDepth) {
    const trimmed = (result as string).trim();
    if (!trimmed) break;
    try {
      result = JSON.parse(trimmed);
    } catch {
      break;
    }
    depth += 1;
  }
  return result;
};

//#region Playback Mode
const mode = computed(() => (route.path.includes('/playback') ? 'playback' : 'live'));
const isPlaybackMode = computed(() => mode.value === 'playback');
const isMonitorMode = computed(() => route.path.includes('/monitor'));

// 场景标题
const title = ref<string>('');
// 新增：用于存储多楼层场景数据的数组
const floorScenes = ref<any[]>([]);
// 新增：当前楼层的索引
const currentFloorIndex = ref(0);

// 新增：判断是否为多楼层模式
const isMultiFloor = computed(() => floorScenes.value.length > 1);

const modeTitle = computed(() => {
  if (isPlaybackMode.value) {
    return '场景回放';
  }
  return isMonitorMode.value ? '场景监控' : '场景仿真';
});

// 左侧侧边栏折叠状态
const leftCollapsed = ref<boolean>(false);

// 监听标题变化，动态更新页面标题
watch(
  modeTitle,
  (newTitle) => {
    document.title = newTitle;
  },
  { immediate: true },
);

// 服务实例
const container = shallowRef<HTMLDivElement>();
const editor = shallowRef<EditorService>();
const storageLocationService = shallowRef<StorageLocationService>();
const client = shallowRef<WebSocket>();
// 模拟门设备WS推送（仅开发调试使用）
let doorMockTimer: number | undefined;
let doorMockStatus: 0 | 1 = 0;
// 模拟门设备断连/连通的节奏控制
let doorMockTick = 0;
// 左侧边栏元素引用（用于 Ctrl/Cmd+F 聚焦搜索框）
const leftSiderEl = shallowRef<HTMLElement>();
const isPlaybackControllerVisible = ref<boolean>(true);
const selectedDate = ref<Dayjs>();

const playback = usePlaybackWebSocket(editor);

watch(mode, async (newMode) => {
  if (newMode === 'live') {
    playback.disconnect();
    await monitorScene();
  } else {
    client.value?.close();
    playback.connect(props.sid); // Connect once using props.sid
  }
});

watch(playback.sceneJson, async (newJson) => {
  if (newJson) {
    await editor.value?.load(newJson);
    // [关键修复] 场景加载后，立即重新初始化机器人
    await editor.value?.initRobots();
  }
});

watch(selectedDate, (date) => {
  if (date) {
    const startOfDayTimestamp = date.startOf('day').valueOf();
    playback.seek(startOfDayTimestamp);
    // The total duration is now relative to the start of the day
    playback.totalDuration.value = 24 * 60 * 60 * 1000;
    // Set current time to the beginning of the selected day for the slider
    playback.currentTime.value = startOfDayTimestamp;
  }
});

const handleSeek = (relativeTime: number) => {
  if (selectedDate.value) {
    const absoluteTimestamp = relativeTime + selectedDate.value.startOf('day').valueOf();
    // 乐观更新，立即将前端时间状态同步到用户选择的时间点
    playback.currentTime.value = absoluteTimestamp;
    playback.seek(absoluteTimestamp);
  }
};

const handleDateChange = (date: Dayjs) => {
  selectedDate.value = date;
};

// 监听后台推送的绝对时间戳，如果跨天，则自动更新UI上的日期选择器
watch(playback.currentTime, (newTimestamp) => {
  if (newTimestamp > 0 && selectedDate.value) {
    const newDate = dayjs(newTimestamp);
    if (!newDate.isSame(selectedDate.value, 'day')) {
      selectedDate.value = newDate;
    }
  }
});

watch(playback.sceneJson, async (newJson) => {
  if (newJson) {
    await editor.value?.load(newJson);
    // [关键修复] 场景加载后，立即重新初始化机器人
    await editor.value?.initRobots();
  }
});

//#endregion

// 依赖注入
provide(EDITOR_KEY, editor);
//#endregion

//#region 核心服务接口
/**
 * 读取场景数据
 */
const readScene = async () => {
  const res = props.id ? await getSceneByGroupId(props.id, props.sid) : await getSceneById(props.sid);
  title.value = res?.label ?? '';
  const sceneJson = normalizeSceneJson(res?.json);

  // 检查返回的json是数组（多楼层）还是对象（单楼层）
  if (Array.isArray(sceneJson)) {
    if (sceneJson.length > 0) {
      // 多楼层
      floorScenes.value = sceneJson as any[];
      currentFloorIndex.value = 0; // 默认显示第一层
      await editor.value?.load(floorScenes.value[0]);
    } else {
      // 空数组，当作空场景处理
      message.warn('场景文件为空数组');
      floorScenes.value = [{}];
      currentFloorIndex.value = 0;
      await editor.value?.load('{}'); // 加载空场景
    }
  } else if (sceneJson && typeof sceneJson === 'object' && Object.keys(sceneJson as any).length > 0) {
    // 单楼层，包装成数组以统一处理
    floorScenes.value = [sceneJson];
    currentFloorIndex.value = 0;
    await editor.value?.load(sceneJson as any);
  } else {
    // 空对象或null/undefined，也当作空场景处理
    message.warn('场景文件为空或格式不正确');
    floorScenes.value = [{}];
    currentFloorIndex.value = 0;
    await editor.value?.load('{}'); // 加载空场景
  }
};

/**
 * 监控场景中的机器人状态
 * 采用 requestAnimationFrame 优化高频数据渲染，确保动画流畅且不阻塞UI线程。
 */
const monitorScene = async () => {
  console.log(current.value?.id);
  client.value?.close();
  // 根据路由路径是否是真实场景监控决定使用哪个监控接口
  const sidWithFloor = isMultiFloor.value ? `${props.sid}/${currentFloorIndex.value + 1}` : props.sid;
  const ws = isMonitorMode.value ? await monitorRealSceneById(sidWithFloor) : await monitorSceneById(sidWithFloor);
  if (isNil(ws)) return;

  // 使用 Map 来缓冲每个机器人最新的实时数据。
  // Key 是机器人 ID，Value 是该机器人最新的完整信息。
  // 这样做可以确保我们总是有每个机器人最新的状态，并且可以快速更新，避免重复渲染。
  const latestRobotData = new Map<string, RobotRealtimeInfo>();

  // 用于存储 requestAnimationFrame 的 ID，方便在组件卸载或 WebSocket 关闭时取消动画循环。
  let animationFrameId: number;

  /**
   * 批量更新机器人数据，减少渲染调用次数
   * @param updates 需要更新的机器人数据数组
   */
  const batchUpdateRobots = (updates: Array<{ id: string; data: RobotRealtimeInfo }>) => {
    if (!editor.value || updates.length === 0) return;

    // 用于收集所有图元（pen）的更新数据
    const allPenUpdates: any[] = [];

    updates.forEach(({ id, data }) => {
      const {
        x,
        y,
        active,
        angle,
        path: points,
        isWaring,
        isFault,
        isCharging = 0,
        isCarrying = 0,
        canOrder,
        ...rest
      } = data;

      // 1. 更新机器人缓存的业务数据
      editor.value?.updateRobot(id, {
        ...rest,
        isCharging: isCharging as any,
        isCarrying: isCarrying as any,
        canOrder: canOrder as any,
      });

      // 2. 准备图元（pen）的更新负载对象，将多个更新合并
      const penUpdatePayload: any = { id };
      const robotState: any = {};

      // 2.1 处理路径并将其放入 robotState
      // 处理路径坐标转换，参考refreshRobot方法的逻辑
      if (points?.length && !isMonitorMode.value) {
        // 新路径：相对于机器人中心的坐标
        const cx = x || 37; // 机器人中心X坐标，默认37
        const cy = y || 37; // 机器人中心Y坐标，默认37
        robotState.path = points.map((p) => ({ x: p.x - cx, y: p.y - cy }));
      }

      // 2.2 合并其他机器人状态
      if (active !== undefined) robotState.active = active;
      if (isWaring !== undefined) robotState.isWaring = isWaring;
      if (isFault !== undefined) robotState.isFault = isFault;
      if (isCharging !== undefined) robotState.isCharging = isCharging;
      if (isCarrying !== undefined) robotState.isCarrying = isCarrying;
      if (canOrder !== undefined) robotState.canOrder = canOrder;

      // 将合并后的状态赋给 payload
      if (Object.keys(robotState).length > 0) {
        penUpdatePayload.robot = robotState;
      }

      // 2.3 合并位置、可见性和角度
      if (!isNil(x) && !isNil(y)) {
        penUpdatePayload.x = x - 60;
        penUpdatePayload.y = y - 60;
        penUpdatePayload.visible = true;
        penUpdatePayload.locked = LockState.None;
      }
      if (angle != null) {
        penUpdatePayload.rotate = -angle + 180;
      }

      // 只有当有实际更新时才推入数组
      if (Object.keys(penUpdatePayload).length > 1) {
        allPenUpdates.push(penUpdatePayload);
      }

      // 3. 更新状态覆盖图标 (此API调用如果不能合并，则保留在循环内)
      //    使用刚刚为机器人计算出的新位置来更新覆盖物，以确保同步
      const newPositionForOverlay =
        penUpdatePayload.x !== undefined && penUpdatePayload.y !== undefined
          ? { x: penUpdatePayload.x, y: penUpdatePayload.y, rotate: penUpdatePayload.rotate }
          : undefined;
      editor.value?.updateRobotStatusOverlay?.(id, false, newPositionForOverlay);
    });

    // 4. 使用Meta2D的批量更新方法一次性提交所有更改
    if (allPenUpdates.length > 0) {
      allPenUpdates.forEach((update) => {
        editor.value?.setValue(update, { render: false, history: false, doEvent: false });
      });
    }

    // 5. 批量更新完成后，统一渲染一次
    /*
  为了让机器人在地图上最基本地被绘制出来并能够移动，后台推送的 WebSocket 数据最少需要 id, x, y 这 3     
  个字段。

  为了达到一个功能上比较完整的视觉效果（带光圈、能旋转），则最少需要 id, x, y, angle, active 这 5       
  个字段。
    */
    editor.value?.render();
  };

  /**
   * 渲染循环函数。
   * 通过 requestAnimationFrame 以浏览器的刷新频率被调用。
   * 采用时间分片（Time Slicing）策略，为每一帧的渲染操作设定一个时间预算（frameBudget），
   * 避免单帧处理过多数据导致UI阻塞。
   * 新增批量渲染优化，减少渲染调用次数。
   */
  const renderLoop = () => {
    const frameBudget = 8; // 每一帧的渲染预算，单位：毫秒。保守设置为8ms，为其他任务留出时间。
    const startTime = performance.now();

    // 收集所有需要更新的机器人数据，但不立即渲染
    const updates: Array<{ id: string; data: RobotRealtimeInfo }> = [];

    // 在时间预算内，持续收集机器人数据
    while (performance.now() - startTime < frameBudget && latestRobotData.size > 0) {
      // 获取并移除 Map 中的第一条数据
      const entry = latestRobotData.entries().next().value;
      if (!entry) break;
      const [id, data] = entry;
      latestRobotData.delete(id);

      // 确保机器人仍然存在于编辑器中
      if (editor.value?.checkRobotById(id)) {
        updates.push({ id, data });
      }
    }

    // 批量更新机器人，减少渲染调用次数
    if (updates.length > 0) {
      batchUpdateRobots(updates);
    }

    // 处理缓冲的自动门点数据
    autoDoorSimulationService.processBufferedData(frameBudget, startTime);

    // 请求浏览器在下一次重绘之前再次调用 renderLoop，形成动画循环。
    // 即使本帧没有处理完所有数据，下一帧也会继续处理剩余的数据。
    animationFrameId = requestAnimationFrame(renderLoop);
  };

  /**
   * WebSocket 的 onmessage 事件处理器。
   * 这个函数只做一件事：接收数据并将其快速存入缓冲区。
   * 这种"数据与渲染分离"的设计是避免UI阻塞的关键。
   */
  ws.onmessage = (e) => {
    const data = JSON.parse(e.data || '{}');

    // 判断数据类型：type=99为自动门点，其他为机器人
    if (data.type === 99) {
      // 自动门点数据处理
      autoDoorSimulationService.handleWebSocketData(data as AutoDoorWebSocketData);
    } else {
      // 机器人数据处理
      const robotData = data as RobotRealtimeInfo;
      latestRobotData.set(robotData.id, robotData);
    }
  };

  client.value = ws;

  // WebSocket 连接成功后，立即启动渲染循环。
  renderLoop();

  // 增强 WebSocket 的 onclose 事件，以确保在连接关闭时清理资源。
  const originalOnClose = ws.onclose;
  ws.onclose = (event) => {
    // 停止渲染循环，防止不必要的计算和内存泄漏。
    cancelAnimationFrame(animationFrameId);
    // 如果原始的 onclose 处理函数存在，则继续执行它。
    if (originalOnClose) {
      originalOnClose.call(ws, event);
    }
  };

  // 开发/开启DOOR_MOCK时，添加模拟门设备WS推送
  try {
    // DEV 或显式开启 localStorage.DOOR_MOCK === '1' 时启用模拟
    const enableMock =
      (typeof localStorage !== 'undefined' && localStorage.getItem('DOOR_MOCK') === '1') ||
      import.meta?.env?.DEV === true;
    if (enableMock) {
      if (doorMockTimer) window.clearInterval(doorMockTimer);

      // 优先使用当前场景中第一个“门区域”的设备ID作为模拟目标；找不到则回退 door1
      let mockDeviceId = 'door1';
      try {
        const areas: any[] = (editor.value?.find('area') || []) as any[];
        const doorAreas = areas.filter((a: any) => (a.area?.type as any) === (DOOR_AREA_TYPE as any));
        const first = doorAreas[0];
        const did = first?.area?.doorDeviceId;
        if (did) mockDeviceId = did;
      } catch {
        //
      }
      doorMockTimer = window.setInterval(() => {
        doorMockTick++;
        // 每 7 个周期（约14秒）有 2 个周期（约4秒）模拟断连
        const phase = doorMockTick % 7;
        const mockIsConnected = !(phase === 0 || phase === 1);
        // 仅在连接状态下切换开/关门，断连时保持关门（0）
        if (mockIsConnected) {
          doorMockStatus = doorMockStatus === 0 ? 1 : 0;
        } else {
          doorMockStatus = 0;
        }
        const mock: AutoDoorWebSocketData = {
          gid: 'mock-gid',
          id: mockDeviceId,
          label: mockDeviceId,
          brand: null,
          type: 99,
          ip: null,
          battery: 100,
          isConnected: mockIsConnected,
          state: 0,
          canOrder: true,
          canStop: null,
          canControl: true,
          targetPoint: null,
          deviceStatus: doorMockStatus,
          x: 0,
          y: 0,
          active: true,
          angle: 0,
          isWaring: null,
          isFault: null,
          isLoading: null,
          path: [],
        };
        autoDoorSimulationService.handleWebSocketData(mock);
      }, 2000);
    }
  } catch {
    //
  }
};
//#endregion

//#region 服务初始化
onMounted(() => {
  editor.value = new EditorService(container.value!, props.sid);

  // 将 editor 存储到 store 中
  editorStore.setEditor(editor as ShallowRef<EditorService>);

  // 提前注入自动门服务的编辑器实例，确保WS/模拟数据到达时可立即落盘到区域/路段
  try {
    if (editor.value) {
      console.log('[DoorWS] bind editor to door service');
      autoDoorSimulationService.setEditorService(editor.value);
    }
  } catch {
    //
  }

  storageLocationService.value = new StorageLocationService(editor.value, props.sid);
  container.value?.addEventListener('pointerdown', handleCanvasPointerDown, true);
});
//#endregion

//#region 生命周期管理
onMounted(async () => {
  console.log('Current route in movement-supervision.vue:', window.location.href);
  if (mode.value === 'live') {
    await readScene();
    await monitorScene();
  } else {
    playback.connect(props.sid);
    // [调试代码] 自动设置回放日期和时间
    const debugDate = dayjs('2025-09-28 09:00:00');
    selectedDate.value = debugDate;
    playback.seek(debugDate.valueOf());
  }

  await editor.value?.initRobots();

  storageLocationService.value?.startMonitoring({ interval: 1 });
  // 自动保存和恢复视图状态
  await handleAutoSaveAndRestoreViewState();

  // 设置编辑器服务
  if (editor.value) {
    autoDoorSimulationService.setEditorService(editor.value);
  }

  // 订阅右键菜单状态变化
  contextMenuManager.subscribe((state) => {
    contextMenuState.value = state;
  });

  // 添加全局点击事件监听器，用于关闭右键菜单
  document.addEventListener('click', handleGlobalClick);
  document.addEventListener('keydown', handleGlobalKeydown);
  // 监听 Ctrl/Cmd+F 聚焦左侧搜索框
  document.addEventListener('keydown', focusFindKeydownHandler);
});

onUnmounted(() => {
  client.value?.close();
  playback.disconnect(); // Also disconnect playback WS on unmount
  storageLocationService.value?.destroy();
  // 清理自动门点服务（清空缓冲数据）
  autoDoorSimulationService.clearBufferedData();
  if (doorMockTimer) {
    window.clearInterval(doorMockTimer);
    doorMockTimer = undefined;
  }

  // 移除EditorService事件监听器
  if (editor.value) {
    (editor.value as any).off('customContextMenu', (event: Record<string, unknown>) => {
      handleEditorContextMenu(event);
    });
  }

  container.value?.removeEventListener('pointerdown', handleCanvasPointerDown, true);

  if (rightClickGuardTimer) {
    clearTimeout(rightClickGuardTimer);
    rightClickGuardTimer = undefined;
  }

  // 移除全局事件监听器
  document.removeEventListener('click', handleGlobalClick);
  document.removeEventListener('keydown', handleGlobalKeydown);
  document.removeEventListener('keydown', focusFindKeydownHandler);
});
//#endregion

//#region 选择状态管理
const current = ref<{ type: 'robot' | 'point' | 'line' | 'area'; id: string }>();

// 监听编辑器选择状态变化
watch(
  () => editor.value?.selected.value[0],
  (v) => {
    // 如果右键菜单正在显示，则不更新选中状态
    if (contextMenuState.value.isRightClickActive) {
      return;
    }

    const pen = editor.value?.getPenById(v);
    if (pen?.id) {
      current.value = { type: <'point' | 'line' | 'area'>pen.name, id: pen.id };
      return;
    }
    if (current.value?.type === 'robot') return;
    current.value = undefined;
  },
);

// 计算当前选择的对象类型
const isRobot = computed(() => current.value?.type === 'robot');
const isPoint = computed(() => current.value?.type === 'point');
const isRoute = computed(() => current.value?.type === 'line');
const isArea = computed(() => current.value?.type === 'area');

/**
 * 选择机器人
 * @param id 机器人ID
 */
const selectRobot = (id: string) => {
  current.value = { type: 'robot', id };
  editor.value?.inactive();

  // [FIX] In playback mode, robot pens might not be correctly set as visible or unlocked.
  // This ensures that gotoById can focus on the robot.
  if (isPlaybackMode.value) {
    editor.value?.setValue(
      {
        id,
        visible: true,
        locked: LockState.None,
      },
      { render: false, history: false },
    );
  }

  // 聚焦到机器人位置
  editor.value?.gotoById(id);
};
//#endregion

//#region 视图状态管理
const { saveViewState, autoSaveAndRestoreViewState, isSaving, jumpToPosition, calculateCenterPoint } = useViewState();

/**
 * 保存当前视图状态
 */
const handleSaveViewState = async () => {
  if (!editor.value) return;

  try {
    await saveViewState(editor.value, props.sid, props.id);
    message.success('视图状态保存成功');
  } catch {
    message.error('视图状态保存失败');
  }
};

/**
 * 自动保存和恢复视图状态
 */
const handleAutoSaveAndRestoreViewState = async () => {
  if (!editor.value) return;

  await autoSaveAndRestoreViewState(editor.value, props.sid, props.id);
};
//#endregion

//#region 楼层切换
const handleFloorChange = async (value: any) => {
  const newFloorIndex = value as number;
  if (editor.value && floorScenes.value[newFloorIndex]) {
    // 1) 先加载新楼层
    await editor.value.load(floorScenes.value[newFloorIndex]);

    // 2) 立即定位到新楼层中心，提升交互反馈（不要等待后续WS/机器人初始化）
    try {
      const { centerX, centerY } = calculateCenterPoint(editor.value);
      await jumpToPosition(editor.value, centerX, centerY, false, 0.05);
    } catch {
      // ignore
    }

    // 3) 后续耗时操作异步进行，避免阻塞定位体验
    // 3.1 重新初始化机器人
    Promise.resolve(editor.value.initRobots()).catch(() => void 0);

    // 3.2 重新连接 WebSocket 以获取新楼层的实时数据（不等待握手完成）
    if (mode.value === 'live') {
      void monitorScene();
    }
  }
};
//#endregion

//#region UI状态管理
const show = ref<boolean>(true);

// 右键菜单状态管理 - 使用组合式函数
const contextMenuManager = createContextMenuManager();
const contextMenuState = ref<ContextMenuState>(contextMenuManager.getState());
let rightClickGuardTimer: ReturnType<typeof setTimeout> | undefined;

/**
 * Guard selection state before Meta2D processes the contextmenu event.
 */
const handleCanvasPointerDown = (event: PointerEvent) => {
  if (event.button !== 2) return;

  console.log('[排查] 1. handleCanvasPointerDown 触发，直接处理右键逻辑');
  // 阻止事件继续传播，防止 Meta2d 库的默认行为抑制 contextmenu 事件
  event.preventDefault();
  event.stopPropagation();

  // 手动构建事件对象，模拟从 EditorService 接收到的数据
  const penData = {
    e: event,
    clientRect: {
      x: event.clientX,
      y: event.clientY,
      width: 0,
      height: 0,
      top: event.clientY,
      right: event.clientX,
      bottom: event.clientY,
      left: event.clientX,
    },
    pen: editor.value?.store.hover, // 从 editor store 获取当前悬停的元素
  };

  // 直接调用右键菜单处理函数
  handleEditorContextMenu(penData);
};

//#endregion

// 返回到父级 iframe 的场景卡片
const backToCards = () => {
  window.parent?.postMessage({ type: 'scene_return_to_cards' }, '*');
};

//#region 右键菜单处理
/**
 * 处理EditorService的自定义右键菜单事件
 * @param penData EditorService传递的pen数据
 */
const handleEditorContextMenu = (penData: Record<string, unknown>) => {
  console.log('[排查] 3. handleEditorContextMenu 开始处理事件', penData);
  handleContextMenuFromPenData(penData, contextMenuManager, {
    storageLocationService: storageLocationService.value,
    robotService: editor.value, // 传递EditorService作为机器人服务
  });
};

/**
 * 关闭右键菜单
 */
const handleCloseContextMenu = () => {
  contextMenuManager.close();
  contextMenuManager.setState({ isRightClickActive: false });
};

/**
 * 处理右键菜单操作完成事件
 * @param data 操作数据
 */
const handleActionComplete = (data: any) => {
  console.log('右键菜单操作完成:', data);
  // 操作完成后，关闭菜单，以便下次打开时刷新数据
  handleCloseContextMenu();

  // 如果成功，可以触发一个全局事件或调用一个方法来刷新整个场景的数据
  if (data.success) {
    // 例如: sceneStore.refreshData();
    // 目前，关闭菜单后，用户再次右键会看到最新状态，这已经满足了基本需求。
  }
};

/**
 * 处理全局点击事件，用于关闭右键菜单
 * @param event 点击事件
 */
const handleGlobalClick = (event: MouseEvent) => {
  // 检查是否点击了关闭按钮
  const closeButton = (event.target as Element)?.closest('.close-button');
  if (closeButton) {
    // 如果点击了关闭按钮，让按钮自己的点击事件处理关闭
    return;
  }
};

// 判断事件目标是否在可编辑区域（输入框/文本域/可编辑元素）
const isTypingElement = (el: EventTarget | null) => {
  const node = el as HTMLElement | null;
  if (!node) return false;
  const tag = node.tagName?.toLowerCase();
  return tag === 'input' || tag === 'textarea' || node.isContentEditable === true;
};

// Ctrl/Cmd + F 快捷键聚焦左侧搜索输入框
const focusFindKeydownHandler = (event: KeyboardEvent) => {
  const isFindKey = event.key === 'f' || event.key === 'F';
  if ((event.ctrlKey || event.metaKey) && isFindKey && !isTypingElement(event.target)) {
    const raw: any = leftSiderEl.value as any;
    const sider: HTMLElement | null = raw && raw.$el ? (raw.$el as HTMLElement) : (raw as HTMLElement | null);
    if (sider) {
      const input = sider.querySelector('.search input, .search .ant-input') as HTMLInputElement | null;
      if (input) {
        event.preventDefault();
        input.focus();
        input.select?.();
      }
    }
  }
};

/**
 * 处理全局键盘事件，ESC键关闭右键菜单
 * @param event 键盘事件
 */
const handleGlobalKeydown = (event: KeyboardEvent) => {
  // ESC键关闭右键菜单
  if (event.key === 'Escape' && contextMenuState.value.visible) {
    contextMenuManager.close();
  }
};
//#endregion
</script>

<template>
  <a-layout class="full">
    <a-layout-header class="p-16" style="height: 64px">
      <a-flex justify="space-between" align="center">
        <a-typography-text class="title">{{ title }}--{{ modeTitle }}</a-typography-text>
        <a-space align="center">
          <a-button @click="backToCards"> 返回 </a-button>
          <a-button type="primary" :loading="isSaving" @click="handleSaveViewState"> 保存比例 </a-button>
          <a-divider type="vertical" />

          <a-select
            v-if="isMultiFloor"
            v-model:value="currentFloorIndex"
            style="width: 120px"
            @change="handleFloorChange"
          >
            <a-select-option v-for="(scene, index) in floorScenes" :key="index" :value="index">
              楼层 {{ index + 1 }}
            </a-select-option>
          </a-select>
        </a-space>
      </a-flex>
    </a-layout-header>

    <a-layout class="p-16 main-layout">
      <img
        :src="leftCollapsed ? expandIcon : foldIcon"
        class="sider-toggle"
        alt="toggle-sider"
        @click="leftCollapsed = !leftCollapsed"
      />
      <a-layout-sider
        class="left-sider"
        :width="leftCollapsed ? 0 : 320"
        :style="{ minWidth: leftCollapsed ? '0px' : '320px', overflow: 'hidden' }"
        ref="leftSiderEl"
      >
        <a-tabs type="card" v-show="!leftCollapsed">
          <a-tab-pane key="1" :tab="$t('机器人')">
            <RobotGroups v-if="editor" :token="EDITOR_KEY" :sid="sid" :current="current?.id" @change="selectRobot" />
          </a-tab-pane>
          <a-tab-pane key="1.5" :tab="$t('机器人标签')">
            <RobotLabels v-if="editor" :token="EDITOR_KEY" :sid="sid" :current="current?.id" @change="selectRobot" />
          </a-tab-pane>
          <a-tab-pane key="2" :tab="$t('库位')">
            <PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-storage />
          </a-tab-pane>
          <a-tab-pane key="3" :tab="$t('高级组')">
            <PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" />
          </a-tab-pane>
        </a-tabs>
      </a-layout-sider>
      <a-layout-content>
        <div ref="container" class="editor-container full"></div>
        <!-- 自定义地图工具栏（固定右下角，最小侵入） -->
        <MapToolbar v-if="!isPlaybackMode" :token="EDITOR_KEY" :container-el="container" />
      </a-layout-content>
    </a-layout>
  </a-layout>

  <template v-if="current?.id">
    <a-float-button style="top: 80px; right: 16px" shape="square" @click="show = !show">
      <template #icon><i class="icon detail" /></template>
    </a-float-button>
    <div v-if="show" class="card-container">
      <RobotDetailCard v-if="isRobot" :key="current.id" :token="EDITOR_KEY" :current="current.id" />
      <PointDetailCard
        v-if="isPoint"
        :token="EDITOR_KEY"
        :current="current.id"
        :storage-locations="storageLocationService?.getLocationsByPointId(current.id)"
      />
      <RouteDetailCard v-if="isRoute" :token="EDITOR_KEY" :current="current.id" />
      <AreaDetailCard v-if="isArea" :token="EDITOR_KEY" :current="current.id" />
    </div>
  </template>

  <!-- 视角跟随提示 -->
  <FollowViewNotification />

  <!-- 右键菜单 -->
  <ContextMenu
    :visible="contextMenuState.visible"
    :x="contextMenuState.x"
    :y="contextMenuState.y"
    :menu-type="contextMenuState.menuType"
    :storage-locations="contextMenuState.storageLocations"
    :robot-id="contextMenuState.robotInfo?.id"
    :token="EDITOR_KEY"
    @close="handleCloseContextMenu"
    @action-complete="handleActionComplete"
  />

  <template v-if="isPlaybackMode && selectedDate">
    <a-float-button
      style="right: 24px; bottom: 120px"
      shape="square"
      @click="isPlaybackControllerVisible = !isPlaybackControllerVisible"
    >
      <template #icon>
        <img
          :src="foldIcon2"
          class="fold-icon"
          :class="{ folded: !isPlaybackControllerVisible }"
          alt="toggle-playback"
        />
      </template>
    </a-float-button>

    <!-- Playback Controller -->
    <PlaybackController
      v-if="isPlaybackControllerVisible"
      :is-playing="playback.isPlaying.value"
      :current-time="playback.currentTime.value - (selectedDate?.startOf('day').valueOf() ?? 0)"
      :total-duration="playback.totalDuration.value"
      :selected-date="selectedDate"
      @play="playback.play"
      @pause="playback.pause"
      @seek="handleSeek"
      @date-change="handleDateChange"
    />
  </template>
</template>

<style scoped lang="scss">
.editor-container {
  background-color: transparent !important;
  position: relative;
  z-index: 1;
}

.card-container {
  position: fixed;
  top: 80px;
  right: 64px;
  z-index: 100;
  width: 320px;
  height: calc(100% - 96px);
  overflow: visible;
  overflow-y: auto;
  pointer-events: none;

  & > * {
    pointer-events: all;
  }
}
.main-layout {
  position: relative;
}

.left-sider {
  position: relative;
  left: 36px;
}

.sider-toggle {
  position: absolute;
  top: 20px;
  left: 8px;
  width: 36px;
  height: 36px;
  padding: 8px;
  cursor: pointer;
  z-index: 10;
  background-color: #fff;
  border-radius: 4px;
  html[theme='dark'] & {
    background-color: #5a5d5e;
  }
}
:deep(.ant-tabs-tab) {
  padding: 14px 12px !important;
}

.fold-icon {
  width: 48px;
  height: 48px;
  transition: transform 0.3s ease;

  &.folded {
    transform: rotate(180deg);
  }
}
</style>
