<script setup lang="ts">
import type { RobotRealtimeInfo } from '@api/robot';
import { getSceneByGroupId, getSceneById, monitorRealSceneById, monitorSceneById } from '@api/scene';
import { EditorService } from '@core/editor.service';
import { StorageLocationService } from '@core/storage-location.service';
import { useViewState } from '@core/useViewState';
import { message } from 'ant-design-vue';
import { isNil } from 'lodash-es';
import { computed, onMounted, onUnmounted, provide, ref, shallowRef, watch } from 'vue';
import { useRoute } from 'vue-router';

import { autoDoorSimulationService, type AutoDoorWebSocketData } from '../services/auto-door-simulation.service';

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

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

//#region 响应式状态定义
// 获取路由信息以判断当前模式
const route = useRoute();
const isMonitorMode = computed(() => route.path.includes('/monitor'));

// 场景标题
const title = ref<string>('');

// 监听标题变化，动态更新页面标题
watch(
  isMonitorMode,
  (isMonitor) => {
    const mode = isMonitor ? '场景监控' : '场景仿真';
    document.title = mode;
  },
  { immediate: true },
);

// 服务实例
const container = shallowRef<HTMLDivElement>();
const editor = shallowRef<EditorService>();
const storageLocationService = shallowRef<StorageLocationService>();
const client = shallowRef<WebSocket>();

// 依赖注入
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 ?? '';
  editor.value?.load(res?.json);
};

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

    // 先更新所有机器人的状态信息，包括光圈渲染所需的状态
    updates.forEach(({ id, data }) => {
      const { x, y, active, angle, path: points, isWaring, isFault, ...rest } = data;

      // 更新机器人基本信息
      editor.value?.updateRobot(id, rest);

      // 处理路径坐标转换，参考refreshRobot方法的逻辑
      let processedPath: Array<{ x: number; y: number }> | undefined;
      if (points?.length) {
        // 新路径：相对于机器人中心的坐标
        const cx = x || 37; // 机器人中心X坐标，默认37
        const cy = y || 37; // 机器人中心Y坐标，默认37
        processedPath = points.map((p) => ({ x: p.x - cx, y: p.y - cy }));
      }

      // 更新机器人状态，触发光圈渲染
      const robotState = { active, isWaring, isFault, path: processedPath, angle };
      if (Object.values(robotState).some((v) => v !== undefined)) {
        editor.value?.setValue({ id, robot: robotState }, { render: false, history: false, doEvent: false });
      }
    });

    // 批量设置位置和可见性，只渲染一次
    const positionUpdates = updates.map(({ id, data }) => {
      const { x, y, angle } = data;
      if (isNil(x) || isNil(y)) {
        return { id, visible: false };
      } else {
        const newX = x - 60;
        const newY = y - 60;
        const rotate = angle;
        return { id, x: newX, y: newY, rotate, visible: true };
      }
    });

    // 使用Meta2D的批量更新方法，减少渲染调用
    positionUpdates.forEach((update) => {
      editor.value?.setValue(update, { render: false, history: false, doEvent: false });
    });

    // 批量更新完成后，统一渲染一次
    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);
    }
  };
};
//#endregion

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

//#region 生命周期管理
onMounted(async () => {
  await readScene();
  await editor.value?.initRobots();
  await monitorScene();
  storageLocationService.value?.startMonitoring({ interval: 1 });
  // 自动保存和恢复视图状态
  await handleAutoSaveAndRestoreViewState();

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

    // 注释掉模拟逻辑，使用真实WebSocket数据
    // autoDoorSimulationService.startSimulation({
    //   deviceId: 'test01',
    //   label: 'TestAutoDoor01',
    //   interval: 3000,
    //   initialStatus: 0,
    //   enableLogging: true,
    // });
  }
});

onUnmounted(() => {
  client.value?.close();
  storageLocationService.value?.destroy();
  // 清理自动门点服务（清空缓冲数据）
  autoDoorSimulationService.clearBufferedData();
  // 注释掉模拟相关的清理，只保留WebSocket数据处理的清理
  // autoDoorSimulationService.stopAllSimulations();
});
//#endregion

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

// 监听编辑器选择状态变化
watch(
  () => editor.value?.selected.value[0],
  (v) => {
    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();
  // 聚焦到机器人位置
  editor.value?.gotoById(id);
};
//#endregion

//#region 视图状态管理
const { saveViewState, autoSaveAndRestoreViewState, isSaving } = 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 UI状态管理
const show = ref<boolean>(true);
//#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 }}--{{ isMonitorMode ? '场景监控' : '场景仿真' }}</a-typography-text>
        <a-button type="primary" :loading="isSaving" @click="handleSaveViewState"> 保存比例 </a-button>
      </a-flex>
    </a-layout-header>

    <a-layout class="p-16">
      <a-layout-sider :width="320">
        <a-tabs type="card">
          <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="2" :tab="$t('库区')">
            <PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-area1 />
          </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>
      </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" :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>
</template>

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

.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;
  }
}
</style>
