import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { App, Button, Card, List, Spin, Typography, Tag, Switch, Tree, Empty } from "antd";
import { LeftOutlined, ReloadOutlined, SaveOutlined, MenuFoldOutlined, MenuUnfoldOutlined, CheckOutlined } from "@ant-design/icons";
import { useNavigate, useParams } from "react-router";

import {
  getEditorProjectInfoUsingGet,
  getEditorTaskUsingGet,
  listEditorTasksUsingGet,
  upsertEditorAnnotationUsingPut,
} from "../annotation.api";

type EditorProjectInfo = {
  projectId: string;
  datasetId: string;
  datasetType?: string | null;
  templateId?: string | null;
  labelConfig?: string | null;
  supported: boolean;
  unsupportedReason?: string | null;
};

type EditorTaskListItem = {
  fileId: string;
  fileName: string;
  fileType?: string | null;
  hasAnnotation: boolean;
  annotationUpdatedAt?: string | null;
};

type LsfMessage = {
  type?: string;
  payload?: unknown;
};

type SegmentInfo = {
  idx: number;
  text: string;
  start: number;
  end: number;
  hasAnnotation: boolean;
  lineIndex: number;
  chunkIndex: number;
};

type ApiResponse<T> = {
  code?: number;
  message?: string;
  data?: T;
};

type EditorTaskPayload = {
  id?: number | string;
  data?: Record<string, unknown>;
  annotations?: unknown[];
};

type EditorTaskResponse = {
  task?: EditorTaskPayload;
  segmented?: boolean;
  segments?: SegmentInfo[];
  currentSegmentIndex?: number;
};

type EditorTaskListResponse = {
  content?: EditorTaskListItem[];
  totalElements?: number;
  totalPages?: number;
  page?: number;
  size?: number;
};

type UpsertAnnotationResponse = {
  annotationId?: string;
  updatedAt?: string;
};

type ExportPayload = {
  taskId?: number | string | null;
  fileId?: string | null;
  segmentIndex?: number | string | null;
  annotation?: Record<string, unknown>;
  requestId?: string | null;
};

type SwitchDecision = "save" | "discard" | "cancel";

const LSF_IFRAME_SRC = "/lsf/lsf.html";
const TASK_PAGE_START = 0;
const TASK_PAGE_SIZE = 200;

type NormalizedTaskList = {
  items: EditorTaskListItem[];
  page: number;
  size: number;
  total: number;
  totalPages: number;
};

const resolveSegmentIndex = (value: unknown) => {
  if (value === null || value === undefined) return undefined;
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : undefined;
};

const normalizePayload = (payload: unknown): ExportPayload | undefined => {
  if (!payload || typeof payload !== "object") return undefined;
  return payload as ExportPayload;
};

const resolvePayloadMessage = (payload: unknown) => {
  if (!payload || typeof payload !== "object") return undefined;
  if ("message" in payload && typeof (payload as { message?: unknown }).message === "string") {
    return (payload as { message?: string }).message;
  }
  return undefined;
};

const isRecord = (value: unknown): value is Record<string, unknown> =>
  !!value && typeof value === "object" && !Array.isArray(value);

const normalizeSnapshotValue = (value: unknown, seen: WeakSet<object>): unknown => {
  if (!value || typeof value !== "object") return value;
  const obj = value as object;
  if (seen.has(obj)) return undefined;
  seen.add(obj);
  if (Array.isArray(value)) {
    return value.map((item) => normalizeSnapshotValue(item, seen));
  }
  const record = value as Record<string, unknown>;
  const sorted: Record<string, unknown> = {};
  Object.keys(record)
    .sort()
    .forEach((key) => {
      sorted[key] = normalizeSnapshotValue(record[key], seen);
    });
  return sorted;
};

const stableStringify = (value: unknown) => {
  const normalized = normalizeSnapshotValue(value, new WeakSet<object>());
  return JSON.stringify(normalized);
};

const buildAnnotationSnapshot = (annotation?: Record<string, unknown>) => {
  if (!annotation) return "";
  const cleaned: Record<string, unknown> = { ...annotation };
  delete cleaned.updated_at;
  delete cleaned.updatedAt;
  delete cleaned.created_at;
  delete cleaned.createdAt;
  return stableStringify(cleaned);
};

const buildSnapshotKey = (fileId: string, segmentIndex?: number) =>
  `${fileId}::${segmentIndex ?? "full"}`;

const mergeTaskItems = (base: EditorTaskListItem[], next: EditorTaskListItem[]) => {
  if (next.length === 0) return base;
  const seen = new Set(base.map((item) => item.fileId));
  const merged = [...base];
  next.forEach((item) => {
    if (seen.has(item.fileId)) return;
    seen.add(item.fileId);
    merged.push(item);
  });
  return merged;
};

const normalizeTaskListResponse = (
  response: ApiResponse<EditorTaskListResponse> | null | undefined,
  fallbackPage: number,
): NormalizedTaskList => {
  const content = response?.data?.content;
  const items = Array.isArray(content) ? content : [];
  const size = response?.data?.size ?? TASK_PAGE_SIZE;
  const total = response?.data?.totalElements ?? items.length;
  const totalPages =
    response?.data?.totalPages ?? (size > 0 ? Math.ceil(total / size) : 0);
  const page = response?.data?.page ?? fallbackPage;
  return {
    items,
    page,
    size,
    total,
    totalPages,
  };
};

export default function LabelStudioTextEditor() {
  const { projectId = "" } = useParams();
  const navigate = useNavigate();
  const { message, modal } = App.useApp();

  const origin = useMemo(() => window.location.origin, []);
  const iframeRef = useRef<HTMLIFrameElement | null>(null);
  const initSeqRef = useRef(0);
  const expectedTaskIdRef = useRef<number | null>(null);
  const prefetchSeqRef = useRef(0);
  const exportCheckRef = useRef<{
    requestId: string;
    resolve: (payload?: ExportPayload) => void;
    timer?: number;
  } | null>(null);
  const exportCheckSeqRef = useRef(0);
  const savedSnapshotsRef = useRef<Record<string, string>>({});
  const pendingAutoAdvanceRef = useRef(false);

  const [loadingProject, setLoadingProject] = useState(true);
  const [loadingTasks, setLoadingTasks] = useState(false);
  const [loadingTaskDetail, setLoadingTaskDetail] = useState(false);
  const [saving, setSaving] = useState(false);
  const [segmentSwitching, setSegmentSwitching] = useState(false);

  const [iframeReady, setIframeReady] = useState(false);
  const [lsReady, setLsReady] = useState(false);
  const [project, setProject] = useState<EditorProjectInfo | null>(null);
  const [tasks, setTasks] = useState<EditorTaskListItem[]>([]);
  const [taskPage, setTaskPage] = useState(TASK_PAGE_START);
  const [taskTotal, setTaskTotal] = useState(0);
  const [taskTotalPages, setTaskTotalPages] = useState(0);
  const [loadingMore, setLoadingMore] = useState(false);
  const [prefetching, setPrefetching] = useState(false);
  const [selectedFileId, setSelectedFileId] = useState<string>("");
  const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
  const [autoSaveOnSwitch, setAutoSaveOnSwitch] = useState(false);

  // 分段相关状态
  const [segmented, setSegmented] = useState(false);
  const [segments, setSegments] = useState<SegmentInfo[]>([]);
  const [currentSegmentIndex, setCurrentSegmentIndex] = useState(0);
  const isTextProject = useMemo(
    () => (project?.datasetType || "").toUpperCase() === "TEXT",
    [project?.datasetType],
  );

  const focusIframe = useCallback(() => {
    const iframe = iframeRef.current;
    if (!iframe) return;
    iframe.focus();
    iframe.contentWindow?.focus?.();
  }, []);

  const postToIframe = useCallback((type: string, payload?: unknown) => {
    const win = iframeRef.current?.contentWindow;
    if (!win) return;
    win.postMessage({ type, payload }, origin);
  }, [origin]);

  const loadProject = useCallback(async () => {
    setLoadingProject(true);
    try {
      const resp = (await getEditorProjectInfoUsingGet(projectId)) as ApiResponse<EditorProjectInfo>;
      const data = resp?.data;
      if (!data?.projectId) {
        message.error("获取标注项目信息失败");
        setProject(null);
        return;
      }
      setProject(data);
    } catch (e) {
      console.error(e);
      message.error("获取标注项目信息失败");
      setProject(null);
    } finally {
      setLoadingProject(false);
    }
  }, [message, projectId]);

  const updateTaskSelection = useCallback((items: EditorTaskListItem[]) => {
    const defaultFileId =
      items.find((item) => !item.hasAnnotation)?.fileId || items[0]?.fileId || "";
    setSelectedFileId((prev) => {
      if (prev && items.some((item) => item.fileId === prev)) return prev;
      return defaultFileId;
    });
  }, []);

  const startPrefetchTasks = useCallback((startPage: number, totalPages: number) => {
    if (!projectId) return;
    if (startPage >= totalPages) {
      setPrefetching(false);
      return;
    }
    const seq = ++prefetchSeqRef.current;
    setPrefetching(true);
    const run = async () => {
      for (let page = startPage; page < totalPages; page += 1) {
        if (prefetchSeqRef.current !== seq) return;
        try {
          const params = {
            page,
            size: TASK_PAGE_SIZE,
            ...(isTextProject ? { excludeSourceDocuments: true } : {}),
          };
          const resp = (await listEditorTasksUsingGet(projectId, {
            ...params,
          })) as ApiResponse<EditorTaskListResponse>;
          const normalized = normalizeTaskListResponse(resp, page);
          setTasks((prev) => mergeTaskItems(prev, normalized.items));
          setTaskPage((prev) => Math.max(prev, normalized.page));
          setTaskTotal(normalized.total);
          setTaskTotalPages(normalized.totalPages);
        } catch (e) {
          console.error(e);
          break;
        }
      }
      if (prefetchSeqRef.current === seq) {
        setPrefetching(false);
      }
    };
    void run();
  }, [isTextProject, projectId]);

  const loadTasks = useCallback(async (options?: {
    mode?: "reset" | "append";
    silent?: boolean;
  }) => {
    if (!projectId) return;
    const mode = options?.mode ?? "reset";
    const silent = options?.silent ?? false;
    if (mode === "append" && taskTotalPages > 0 && taskPage + 1 >= taskTotalPages) {
      return;
    }
    if (mode === "reset") {
      prefetchSeqRef.current += 1;
      setPrefetching(false);
    }
    if (mode === "append") {
      setLoadingMore(true);
    } else if (!silent) {
      setLoadingTasks(true);
    }
    try {
      const nextPage = mode === "append" ? taskPage + 1 : TASK_PAGE_START;
      const params = {
        page: nextPage,
        size: TASK_PAGE_SIZE,
        ...(isTextProject ? { excludeSourceDocuments: true } : {}),
      };
      const resp = (await listEditorTasksUsingGet(projectId, {
        ...params,
      })) as ApiResponse<EditorTaskListResponse>;
      const normalized = normalizeTaskListResponse(resp, nextPage);
      if (mode === "append") {
        setTasks((prev) => mergeTaskItems(prev, normalized.items));
        setTaskPage((prev) => Math.max(prev, normalized.page));
      } else {
        setTasks(normalized.items);
        setTaskPage(normalized.page);
        updateTaskSelection(normalized.items);
      }
      setTaskTotal(normalized.total);
      setTaskTotalPages(normalized.totalPages);
      startPrefetchTasks(normalized.page + 1, normalized.totalPages);
    } catch (e) {
      console.error(e);
      if (!silent && mode !== "append") message.error("获取文件列表失败");
      if (mode === "reset") {
        setTasks([]);
        setTaskPage(TASK_PAGE_START);
        setTaskTotal(0);
        setTaskTotalPages(0);
      }
    } finally {
      if (mode === "append") {
        setLoadingMore(false);
      } else if (!silent) {
        setLoadingTasks(false);
      }
    }
  }, [
    isTextProject,
    message,
    projectId,
    startPrefetchTasks,
    taskPage,
    taskTotalPages,
    updateTaskSelection,
  ]);

  const initEditorForFile = useCallback(async (fileId: string, segmentIdx?: number) => {
    if (!project?.supported) return;
    if (!project?.labelConfig) {
      message.error("该项目未绑定标注模板，无法加载编辑器");
      return;
    }
    if (!iframeReady) return;

    const seq = ++initSeqRef.current;
    setLoadingTaskDetail(true);
    setLsReady(false);
    expectedTaskIdRef.current = null;

    try {
      const resp = (await getEditorTaskUsingGet(projectId, fileId, {
        segmentIndex: segmentIdx,
      })) as ApiResponse<EditorTaskResponse>;
      const data = resp?.data;
      const task = data?.task;
      if (!task) {
        message.error("获取任务详情失败");
        return;
      }
      if (seq !== initSeqRef.current) return;

      // 更新分段状态
      const segmentIndex = data?.segmented
        ? resolveSegmentIndex(data.currentSegmentIndex) ?? 0
        : undefined;
      if (data?.segmented) {
        setSegmented(true);
        setSegments(data.segments || []);
        setCurrentSegmentIndex(segmentIndex ?? 0);
      } else {
        setSegmented(false);
        setSegments([]);
        setCurrentSegmentIndex(0);
      }

      const taskData = {
        ...(task?.data || {}),
        file_id: fileId,
        fileId: fileId,
      };
      if (data?.segmented) {
        const normalizedIndex = segmentIndex ?? 0;
        taskData.segment_index = normalizedIndex;
        taskData.segmentIndex = normalizedIndex;
      }
      const taskForIframe = {
        ...task,
        data: taskData,
      };

      const annotations = Array.isArray(taskForIframe.annotations) ? taskForIframe.annotations : [];
      const initialAnnotation = annotations.length > 0 && isRecord(annotations[0]) ? annotations[0] : undefined;
      savedSnapshotsRef.current[buildSnapshotKey(fileId, segmentIndex)] =
        buildAnnotationSnapshot(initialAnnotation);

      expectedTaskIdRef.current = Number(taskForIframe?.id) || null;
      postToIframe("LS_INIT", {
        labelConfig: project.labelConfig,
        task: taskForIframe,
        user: { id: "datamate" },
        // 完整的 Label Studio 原生界面配置
        interfaces: [
          // 核心面板
          "panel",              // 导航面板（undo/redo/reset）
          "update",             // 更新按钮
          "submit",             // 提交按钮
          "controls",           // 控制面板
          // 侧边栏（包含 Outliner 和 Details）
          "side-column",
          // 标注管理
          "annotations:tabs",
          "annotations:menu",
          "annotations:current",
          "annotations:add-new",
          "annotations:delete",
          "annotations:view-all",
          // 预测
          "predictions:tabs",
          "predictions:menu",
          // 其他
          "auto-annotation",
          "edit-history",
        ],
        selectedAnnotationIndex: 0,
        allowCreateEmptyAnnotation: true,
      });
    } catch (e) {
      console.error(e);
      message.error("加载编辑器失败");
    } finally {
      if (seq === initSeqRef.current) setLoadingTaskDetail(false);
    }
  }, [iframeReady, message, postToIframe, project, projectId]);

  const advanceAfterSave = useCallback(async (fileId: string, segmentIndex?: number) => {
    if (!fileId) return;
    if (segmented && segments.length > 0) {
      const sortedSegmentIndices = segments
        .map((seg) => seg.idx)
        .sort((a, b) => a - b);
      const baseIndex = segmentIndex ?? currentSegmentIndex;
      const currentPos = sortedSegmentIndices.indexOf(baseIndex);
      const nextSegmentIndex =
        currentPos >= 0 ? sortedSegmentIndices[currentPos + 1] : sortedSegmentIndices[0];
      if (nextSegmentIndex !== undefined) {
        await initEditorForFile(fileId, nextSegmentIndex);
        return;
      }
    }

    if (tasks.length === 0) {
      message.info("暂无可跳转的数据");
      return;
    }
    const currentFileIndex = tasks.findIndex((item) => item.fileId === fileId);
    const nextTask = currentFileIndex >= 0 ? tasks[currentFileIndex + 1] : tasks[0];
    if (nextTask?.fileId) {
      setSelectedFileId(nextTask.fileId);
      return;
    }
    message.info("已是最后一个数据");
  }, [
    currentSegmentIndex,
    initEditorForFile,
    message,
    segmented,
    segments,
    tasks,
  ]);

  const saveFromExport = useCallback(async (
    payload?: ExportPayload | null,
    options?: { autoAdvance?: boolean }
  ) => {
    const payloadTaskId = payload?.taskId;
    if (expectedTaskIdRef.current && payloadTaskId) {
      if (Number(payloadTaskId) !== expectedTaskIdRef.current) {
        message.warning("已忽略过期的保存请求");
        return false;
      }
    }
    const fileId = payload?.fileId || selectedFileId;
    const annotation = payload?.annotation;
    if (!fileId || !annotation || typeof annotation !== "object") {
      message.error("导出标注失败：缺少 fileId/annotation");
      return false;
    }
    const payloadSegmentIndex = resolveSegmentIndex(payload?.segmentIndex);
    const segmentIndex =
      payloadSegmentIndex !== undefined
        ? payloadSegmentIndex
        : segmented
          ? currentSegmentIndex
          : undefined;

    setSaving(true);
    try {
      const resp = (await upsertEditorAnnotationUsingPut(projectId, String(fileId), {
        annotation,
        segmentIndex,
      })) as ApiResponse<UpsertAnnotationResponse>;
      const updatedAt = resp?.data?.updatedAt;
      message.success("标注已保存");
      setTasks((prev) =>
        prev.map((item) =>
          item.fileId === String(fileId)
            ? {
                ...item,
                hasAnnotation: true,
                annotationUpdatedAt: updatedAt || item.annotationUpdatedAt,
              }
            : item
        )
      );

      const snapshotKey = buildSnapshotKey(String(fileId), segmentIndex);
      const snapshot = buildAnnotationSnapshot(isRecord(annotation) ? annotation : undefined);
      savedSnapshotsRef.current[snapshotKey] = snapshot;

      // 分段模式下更新当前段落的标注状态
      if (segmented && segmentIndex !== undefined) {
        setSegments((prev) =>
          prev.map((seg) =>
            seg.idx === segmentIndex
              ? { ...seg, hasAnnotation: true }
              : seg
          )
        );
      }
      if (options?.autoAdvance) {
        await advanceAfterSave(String(fileId), segmentIndex);
      }
      return true;
    } catch (e) {
      console.error(e);
      message.error("保存失败");
      return false;
    } finally {
      setSaving(false);
    }
  }, [
    advanceAfterSave,
    currentSegmentIndex,
    message,
    projectId,
    segmented,
    selectedFileId,
  ]);

  const requestExportForCheck = useCallback(() => {
    if (!iframeReady || !lsReady) return Promise.resolve(undefined);
    if (exportCheckRef.current) {
      if (exportCheckRef.current.timer) {
        window.clearTimeout(exportCheckRef.current.timer);
      }
      exportCheckRef.current.resolve(undefined);
      exportCheckRef.current = null;
    }
    const requestId = `check_${Date.now()}_${++exportCheckSeqRef.current}`;
    return new Promise<ExportPayload | undefined>((resolve) => {
      const timer = window.setTimeout(() => {
        if (exportCheckRef.current?.requestId === requestId) {
          exportCheckRef.current = null;
        }
        resolve(undefined);
      }, 3000);
      exportCheckRef.current = {
        requestId,
        resolve,
        timer,
      };
      postToIframe("LS_EXPORT_CHECK", { requestId });
    });
  }, [iframeReady, lsReady, postToIframe]);

  const confirmSaveBeforeSwitch = useCallback(() => {
    return new Promise<SwitchDecision>((resolve) => {
      let resolved = false;
      let modalInstance: { destroy: () => void } | null = null;
      const settle = (decision: SwitchDecision) => {
        if (resolved) return;
        resolved = true;
        resolve(decision);
      };
      const handleDiscard = () => {
        if (modalInstance) modalInstance.destroy();
        settle("discard");
      };
      modalInstance = modal.confirm({
        title: "当前段落有未保存标注",
        content: (
          <div className="flex flex-col gap-2">
            <Typography.Text>切换段落前请先保存当前标注。</Typography.Text>
            <Button type="link" danger style={{ padding: 0, height: "auto" }} onClick={handleDiscard}>
              放弃未保存并切换
            </Button>
          </div>
        ),
        okText: "保存并切换",
        cancelText: "取消",
        onOk: () => settle("save"),
        onCancel: () => settle("cancel"),
      });
    });
  }, [modal]);

  const requestExport = () => {
    if (!selectedFileId) {
      message.warning("请先选择文件");
      return;
    }
    pendingAutoAdvanceRef.current = true;
    postToIframe("LS_EXPORT", {});
  };

  // 段落切换处理
  const handleSegmentChange = useCallback(async (newIndex: number) => {
    if (newIndex === currentSegmentIndex) return;
    if (segmentSwitching || saving || loadingTaskDetail) return;
    if (!iframeReady || !lsReady) {
      message.warning("编辑器未就绪，无法切换段落");
      return;
    }

    setSegmentSwitching(true);
    try {
      const payload = await requestExportForCheck();
      if (!payload) {
        message.warning("无法读取当前标注，已取消切换");
        return;
      }

      const payloadTaskId = payload.taskId;
      if (expectedTaskIdRef.current && payloadTaskId) {
        if (Number(payloadTaskId) !== expectedTaskIdRef.current) {
          message.warning("已忽略过期的标注数据");
          return;
        }
      }

      const payloadFileId = payload.fileId || selectedFileId;
      const payloadSegmentIndex = resolveSegmentIndex(payload.segmentIndex);
      const resolvedSegmentIndex =
        payloadSegmentIndex !== undefined
          ? payloadSegmentIndex
          : segmented
            ? currentSegmentIndex
            : undefined;
      const annotation = isRecord(payload.annotation) ? payload.annotation : undefined;
      const snapshotKey = payloadFileId
        ? buildSnapshotKey(String(payloadFileId), resolvedSegmentIndex)
        : undefined;
      const latestSnapshot = buildAnnotationSnapshot(annotation);
      const lastSnapshot = snapshotKey ? savedSnapshotsRef.current[snapshotKey] : undefined;
      const hasUnsavedChange = snapshotKey !== undefined && lastSnapshot !== undefined && latestSnapshot !== lastSnapshot;

      if (hasUnsavedChange) {
        if (autoSaveOnSwitch) {
          const saved = await saveFromExport(payload);
          if (!saved) return;
        } else {
          const decision = await confirmSaveBeforeSwitch();
          if (decision === "cancel") return;
          if (decision === "save") {
            const saved = await saveFromExport(payload);
            if (!saved) return;
          }
        }
      }

      await initEditorForFile(selectedFileId, newIndex);
    } finally {
      setSegmentSwitching(false);
    }
  }, [
    autoSaveOnSwitch,
    confirmSaveBeforeSwitch,
    currentSegmentIndex,
    iframeReady,
    initEditorForFile,
    loadingTaskDetail,
    lsReady,
    message,
    requestExportForCheck,
    saveFromExport,
    segmented,
    selectedFileId,
    segmentSwitching,
    saving,
  ]);

  useEffect(() => {
    setIframeReady(false);
    setProject(null);
    setTasks([]);
    setTaskPage(TASK_PAGE_START);
    setTaskTotal(0);
    setTaskTotalPages(0);
    setLoadingMore(false);
    prefetchSeqRef.current += 1;
    setPrefetching(false);
    setSelectedFileId("");
    initSeqRef.current = 0;
    setLsReady(false);
    expectedTaskIdRef.current = null;
    // 重置分段状态
    setSegmented(false);
    setSegments([]);
    setCurrentSegmentIndex(0);
    savedSnapshotsRef.current = {};
    if (exportCheckRef.current?.timer) {
      window.clearTimeout(exportCheckRef.current.timer);
    }
    exportCheckRef.current = null;

    if (projectId) loadProject();
  }, [projectId, loadProject]);

  useEffect(() => {
    if (!project?.supported) return;
    loadTasks({ mode: "reset" });
  }, [project?.supported, loadTasks]);

  useEffect(() => {
    if (!selectedFileId) return;
    initEditorForFile(selectedFileId);
  }, [selectedFileId, iframeReady, initEditorForFile]);

  useEffect(() => {
    if (!iframeReady) return;
    focusIframe();
  }, [focusIframe, iframeReady]);

  useEffect(() => {
    if (!lsReady) return;
    focusIframe();
  }, [focusIframe, lsReady]);

  useEffect(() => {
    if (!lsReady) return;
    const handleWindowFocus = () => {
      focusIframe();
    };
    window.addEventListener("focus", handleWindowFocus);
    return () => window.removeEventListener("focus", handleWindowFocus);
  }, [focusIframe, lsReady]);

  const segmentTreeData = useMemo(() => {
    if (!segmented || segments.length === 0) return [];
    const lineMap = new Map<number, SegmentInfo[]>();
    segments.forEach((seg) => {
      const list = lineMap.get(seg.lineIndex) || [];
      list.push(seg);
      lineMap.set(seg.lineIndex, list);
    });
    return Array.from(lineMap.entries())
      .sort((a, b) => a[0] - b[0])
      .map(([lineIndex, lineSegments]) => ({
        key: `line-${lineIndex}`,
        title: `第${lineIndex + 1}行`,
        selectable: false,
        children: lineSegments
          .sort((a, b) => a.chunkIndex - b.chunkIndex)
          .map((seg) => ({
            key: `seg-${seg.idx}`,
            title: (
              <span className="flex items-center gap-1">
                <span>{`片${seg.chunkIndex + 1}`}</span>
                {seg.hasAnnotation && (
                  <CheckOutlined style={{ fontSize: 10, color: "#52c41a" }} />
                )}
              </span>
            ),
          })),
      }));
  }, [segmented, segments]);

  const segmentLineKeys = useMemo(
    () => segmentTreeData.map((item) => String(item.key)),
    [segmentTreeData]
  );

  const handleSegmentSelect = useCallback((keys: Array<string | number>) => {
    const [first] = keys;
    if (first === undefined || first === null) return;
    const key = String(first);
    if (!key.startsWith("seg-")) return;
    const nextIndex = Number(key.replace("seg-", ""));
    if (!Number.isFinite(nextIndex)) return;
    handleSegmentChange(nextIndex);
  }, [handleSegmentChange]);

  useEffect(() => {
    const handler = (event: MessageEvent<LsfMessage>) => {
      if (event.origin !== origin) return;
      const msg = event.data || {};
      if (!msg?.type) return;

      if (msg.type === "LS_IFRAME_READY") {
        setIframeReady(true);
        return;
      }

      const payload = normalizePayload(msg.payload);

      if (msg.type === "LS_READY") {
        const readyTaskId = payload?.taskId;
        if (expectedTaskIdRef.current && readyTaskId) {
          if (Number(readyTaskId) !== expectedTaskIdRef.current) return;
        }
        setLsReady(true);
        return;
      }

      if (msg.type === "LS_EXPORT_RESULT") {
        const shouldAutoAdvance = pendingAutoAdvanceRef.current;
        pendingAutoAdvanceRef.current = false;
        saveFromExport(payload, { autoAdvance: shouldAutoAdvance });
        return;
      }

      if (msg.type === "LS_EXPORT_CHECK_RESULT") {
        const pending = exportCheckRef.current;
        if (!pending) return;
        const requestId = payload?.requestId;
        if (requestId && requestId !== pending.requestId) return;
        if (pending.timer) {
          window.clearTimeout(pending.timer);
        }
        exportCheckRef.current = null;
        pending.resolve(payload);
        return;
      }

      // 兼容 iframe 内部在 submit 时直接上报（若启用）
      if (msg.type === "LS_SUBMIT") {
        saveFromExport(payload, { autoAdvance: false });
        return;
      }

      if (msg.type === "LS_ERROR") {
        const payloadMessage = resolvePayloadMessage(msg.payload);
        message.error(payloadMessage || "编辑器发生错误");
        setLsReady(false);
        pendingAutoAdvanceRef.current = false;
      }
    };

    window.addEventListener("message", handler);
    return () => window.removeEventListener("message", handler);
  }, [message, origin, saveFromExport]);

  const canLoadMore = taskTotalPages > 0 && taskPage + 1 < taskTotalPages;
  const loadMoreNode = canLoadMore ? (
    <div className="p-2 text-center">
      <Button
        size="small"
        loading={loadingMore}
        disabled={loadingTasks || prefetching}
        onClick={() => loadTasks({ mode: "append" })}
      >
        加载更多
      </Button>
      {prefetching && (
        <Typography.Text type="secondary" style={{ fontSize: 10, marginLeft: 8 }}>
          后台加载中...
        </Typography.Text>
      )}
      {taskTotal > 0 && (
        <Typography.Text type="secondary" style={{ fontSize: 10, marginLeft: 8 }}>
          已加载 {tasks.length}/{taskTotal}
        </Typography.Text>
      )}
    </div>
  ) : null;

  if (loadingProject) {
    return (
      <div className="h-full flex items-center justify-center">
        <Spin />
      </div>
    );
  }

  if (!project) {
    return (
      <div className="h-full flex items-center justify-center">
        <Card>
          <Typography.Text>未找到标注项目</Typography.Text>
          <div className="mt-4 flex justify-end">
            <Button onClick={() => navigate("/data/annotation")}>返回</Button>
          </div>
        </Card>
      </div>
    );
  }

  if (!project.supported) {
    return (
      <div className="h-full flex items-center justify-center">
        <Card style={{ maxWidth: 640 }}>
          <Typography.Title level={4}>暂不支持该数据类型</Typography.Title>
          <Typography.Paragraph type="secondary">
            {project.unsupportedReason || "当前仅支持 TEXT/IMAGE 项目的内嵌编辑器。"}
          </Typography.Paragraph>
          <div className="flex justify-end gap-2">
            <Button onClick={() => navigate("/data/annotation")}>返回</Button>
          </div>
        </Card>
      </div>
    );
  }

  return (
    <div className="h-full flex flex-col">
      {/* 顶部工具栏 */}
      <div className="flex items-center justify-between px-3 py-2 border-b border-gray-200 bg-white">
        <div className="flex items-center gap-2">
          <Button icon={<LeftOutlined />} onClick={() => navigate("/data/annotation")}>
            返回
          </Button>
          <Button
            icon={sidebarCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
            onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
            title={sidebarCollapsed ? "展开文件列表" : "收起文件列表"}
          />
          <Typography.Title level={5} style={{ margin: 0 }}>
            标注编辑器
          </Typography.Title>
        </div>
        <div className="flex items-center gap-2">
          <Button
            icon={<ReloadOutlined />}
            loading={loadingTasks}
            onClick={() => loadTasks({ mode: "reset" })}
          >
            刷新
          </Button>
          <Button
            type="primary"
            icon={<SaveOutlined />}
            loading={saving}
            disabled={!iframeReady || !selectedFileId}
            onClick={requestExport}
          >
            保存
          </Button>
        </div>
      </div>

      {/* 主体区域 */}
      <div className="flex flex-1 min-h-0">
        {/* 左侧文件列表 - 可折叠 */}
        <div
          className="border-r border-gray-200 bg-gray-50 flex flex-col transition-all duration-200 min-h-0"
          style={{ width: sidebarCollapsed ? 0 : 240, overflow: "hidden" }}
        >
          <div className="px-3 py-2 border-b border-gray-200 bg-white font-medium text-sm">
            文件列表
          </div>
          <div className="flex-1 min-h-0 overflow-auto">
            <List
              loading={loadingTasks}
              size="small"
              dataSource={tasks}
              loadMore={loadMoreNode}
              renderItem={(item) => (
                <List.Item
                  key={item.fileId}
                  className="cursor-pointer hover:bg-blue-50"
                  style={{
                    background: item.fileId === selectedFileId ? "#e6f4ff" : undefined,
                    padding: "8px 12px",
                    borderBottom: "1px solid #f0f0f0",
                  }}
                  onClick={() => setSelectedFileId(item.fileId)}
                >
                  <div className="flex flex-col w-full gap-1">
                    <Typography.Text ellipsis style={{ fontSize: 13 }}>
                      {item.fileName}
                    </Typography.Text>
                    <div className="flex items-center justify-between">
                      <Typography.Text
                        type={item.hasAnnotation ? "success" : "secondary"}
                        style={{ fontSize: 11 }}
                      >
                        {item.hasAnnotation ? "已标注" : "未标注"}
                      </Typography.Text>
                      {item.annotationUpdatedAt && (
                        <Typography.Text type="secondary" style={{ fontSize: 10 }}>
                          {item.annotationUpdatedAt}
                        </Typography.Text>
                      )}
                    </div>
                  </div>
                </List.Item>
              )}
            />
          </div>
          {segmented && (
            <div className="flex-1 border-t border-gray-200 bg-white flex flex-col min-h-0">
              <div className="px-3 py-2 border-b border-gray-200 bg-gray-50 font-medium text-sm flex items-center justify-between">
                <span>段落/分段</span>
                <Tag color="blue" style={{ margin: 0 }}>
                  {currentSegmentIndex + 1} / {segments.length}
                </Tag>
              </div>
              <div className="flex-1 min-h-0 overflow-auto px-2 py-2">
                {segments.length > 0 ? (
                  <Tree
                    showLine
                    blockNode
                    selectedKeys={
                      segmented ? [`seg-${currentSegmentIndex}`] : []
                    }
                    expandedKeys={segmentLineKeys}
                    onSelect={handleSegmentSelect}
                    treeData={segmentTreeData}
                  />
                ) : (
                  <div className="py-6">
                    <Empty
                      image={Empty.PRESENTED_IMAGE_SIMPLE}
                      description="暂无分段"
                    />
                  </div>
                )}
              </div>
              <div className="px-3 py-2 border-t border-gray-200 flex items-center justify-between">
                <Typography.Text style={{ fontSize: 12 }}>
                  切段自动保存
                </Typography.Text>
                <Switch
                  size="small"
                  checked={autoSaveOnSwitch}
                  onChange={(checked) => setAutoSaveOnSwitch(checked)}
                  disabled={segmentSwitching || saving || loadingTaskDetail || !lsReady}
                />
              </div>
            </div>
          )}
        </div>

        {/* 右侧编辑器 - Label Studio iframe */}
        <div className="flex-1 flex flex-col min-h-0">
          {/* 编辑器区域 */}
          <div className="flex-1 relative" onMouseDown={focusIframe}>
            {(!iframeReady || loadingTaskDetail || (selectedFileId && !lsReady)) && (
              <div className="absolute inset-0 z-10 flex items-center justify-center bg-white/80">
                <Spin
                  tip={
                    !iframeReady
                      ? "编辑器资源加载中..."
                      : loadingTaskDetail
                        ? "任务数据加载中..."
                        : "编辑器初始化中..."
                  }
                />
              </div>
            )}
            <iframe
              ref={iframeRef}
              title="Label Studio Frontend"
              src={LSF_IFRAME_SRC}
              className="w-full h-full border-0"
            />
          </div>
        </div>
      </div>
    </div>
  );
}
