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

import {
  getEditorProjectInfoUsingGet,
  getEditorTaskUsingGet,
  listEditorTasksUsingGet,
  upsertEditorAnnotationUsingPut,
  checkFileVersionUsingGet,
  applyNewVersionUsingPost,
  type FileVersionCheckResponse,
} from "../annotation.api";
import { AnnotationResultStatus } from "../annotation.model";

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;
  annotationStatus?: AnnotationResultStatus | null;
};

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

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;
  totalSegments?: number;
  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;
};

const LSF_IFRAME_SRC = "/lsf/lsf.html";
const TASK_PAGE_START = 0;
const TASK_PAGE_SIZE = 200;
const NO_ANNOTATION_LABEL = "无标注";
const NOT_APPLICABLE_LABEL = "不适用";
const NO_ANNOTATION_CONFIRM_TITLE = "没有标注任何内容";
const NO_ANNOTATION_CONFIRM_OK_TEXT = "设为无标注并保存";
const NOT_APPLICABLE_CONFIRM_TEXT = "设为不适用并保存";
const NO_ANNOTATION_CONFIRM_CANCEL_TEXT = "继续标注";
const SAVE_AND_NEXT_LABEL = "保存并跳转到下一段/下一条";

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 isSaveShortcut = (event: KeyboardEvent) => {
  if (event.defaultPrevented || event.isComposing) return false;
  const key = event.key;
  const code = event.code;
  const isS = key === "s" || key === "S" || code === "KeyS";
  if (!isS) return false;
  if (!(event.ctrlKey || event.metaKey)) return false;
  if (event.shiftKey || event.altKey) return false;
  return true;
};

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 isAnnotationResultEmpty = (annotation?: Record<string, unknown>) => {
  if (!annotation) return true;
  if (!("result" in annotation)) return true;
  const result = (annotation as { result?: unknown }).result;
  if (!Array.isArray(result)) return false;
  return result.length === 0;
};

const resolveTaskStatusMeta = (item: EditorTaskListItem) => {
  if (!item.hasAnnotation) {
    return { text: "未标注", type: "secondary" as const };
  }
  if (item.annotationStatus === AnnotationResultStatus.NO_ANNOTATION) {
    return { text: NO_ANNOTATION_LABEL, type: "warning" as const };
  }
  if (item.annotationStatus === AnnotationResultStatus.NOT_APPLICABLE) {
    return { text: NOT_APPLICABLE_LABEL, type: "warning" as const };
  }
  if (item.annotationStatus === AnnotationResultStatus.IN_PROGRESS) {
    return { text: "标注中", type: "warning" as const };
  }
  return { text: "已标注", type: "success" as const };
};

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 "";
  if (isAnnotationResultEmpty(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 fileVersionCheckSeqRef = 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 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 [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 [segmented, setSegmented] = useState(false);
  const [currentSegmentIndex, setCurrentSegmentIndex] = useState(0);
  const [segmentTotal, setSegmentTotal] = useState(0);
  const isTextProject = useMemo(
    () => (project?.datasetType || "").toUpperCase() === "TEXT",
    [project?.datasetType],
  );
  const segmentIndices = useMemo(() => {
    if (segmentTotal <= 0) return [] as number[];
    return Array.from({ length: segmentTotal }, (_, index) => index);
  }, [segmentTotal]);

  // 文件版本相关状态
  const [fileVersionInfo, setFileVersionInfo] = useState<FileVersionCheckResponse | null>(null);
  const [, setCheckingFileVersion] = useState(false);
  const [usingNewVersion, setUsingNewVersion] = useState(false);

  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 confirmEmptyAnnotationStatus = useCallback(() => {
    return new Promise<AnnotationResultStatus | null>((resolve) => {
      let resolved = false;
      let modalInstance: { destroy: () => void } | null = null;
      const settle = (value: AnnotationResultStatus | null) => {
        if (resolved) return;
        resolved = true;
        resolve(value);
        if (modalInstance) modalInstance.destroy();
      };
      const handleNotApplicable = () => settle(AnnotationResultStatus.NOT_APPLICABLE);
      modalInstance = modal.confirm({
        title: NO_ANNOTATION_CONFIRM_TITLE,
        content: (
          <div className="flex flex-col gap-2">
            <Typography.Text>当前未发现任何标注内容。</Typography.Text>
            <Typography.Text type="secondary">如确认为无标注或不适用，可继续保存。</Typography.Text>
            <Button type="link" style={{ padding: 0, height: "auto" }} onClick={handleNotApplicable}>
              {NOT_APPLICABLE_CONFIRM_TEXT}
            </Button>
          </div>
        ),
        okText: NO_ANNOTATION_CONFIRM_OK_TEXT,
        cancelText: NO_ANNOTATION_CONFIRM_CANCEL_TEXT,
        onOk: () => settle(AnnotationResultStatus.NO_ANNOTATION),
        onCancel: () => settle(null),
      });
    });
  }, [modal]);

  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 isCompleted = (item: EditorTaskListItem) => {
      return item.hasAnnotation;
    };
    const defaultFileId =
      items.find((item) => !isCompleted(item))?.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 isSegmented = !!data?.segmented;
      const segmentIndex = isSegmented
        ? resolveSegmentIndex(data.currentSegmentIndex) ?? 0
        : undefined;
      if (isSegmented) {
        setSegmented(true);
        setCurrentSegmentIndex(segmentIndex ?? 0);
        const totalSegments = Number(data?.totalSegments ?? 0);
        setSegmentTotal(Number.isFinite(totalSegments) && totalSegments > 0 ? totalSegments : 0);
      } else {
        setSegmented(false);
        setCurrentSegmentIndex(0);
        setSegmentTotal(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 checkFileVersion = useCallback(async (fileId: string) => {
    if (!projectId || !fileId) return;
    const seq = ++fileVersionCheckSeqRef.current;
    setCheckingFileVersion(true);
    try {
      const resp = (await checkFileVersionUsingGet(projectId, fileId)) as ApiResponse<FileVersionCheckResponse>;
      if (seq !== fileVersionCheckSeqRef.current) return;
      const data = resp?.data;
      if (data) {
        setFileVersionInfo(data);
        if (data.hasNewVersion) {
          modal.warning({
            title: "文件有新版本",
            content: (
              <div className="flex flex-col gap-2">
                <Typography.Text>
                  文件已更新到新版本（当前版本: {data.currentFileVersion}，标注版本: {data.annotationFileVersion}）。
                </Typography.Text>
                <Typography.Text type="secondary">
                  点击"使用新版本"可清空当前标注并使用最新版本的文件内容。
                </Typography.Text>
              </div>
            ),
            okText: "我知道了",
          });
        }
      }
    } catch (e) {
      console.error("检查文件版本失败", e);
    } finally {
      if (seq === fileVersionCheckSeqRef.current) setCheckingFileVersion(false);
    }
  }, [modal, projectId]);

  const handleUseNewVersion = useCallback(async () => {
    if (!selectedFileId) return;

    modal.confirm({
      title: "确认使用新版本",
      content: (
        <div className="flex flex-col gap-2">
          <Typography.Text>
            确认使用新版本？这将清空当前标注并使用最新版本的文件内容。
          </Typography.Text>
          {fileVersionInfo && (
            <Typography.Text type="secondary">
              当前标注版本: {fileVersionInfo.annotationFileVersion}，最新文件版本: {fileVersionInfo.currentFileVersion}
            </Typography.Text>
          )}
        </div>
      ),
      okText: "确认",
      okType: "danger",
      cancelText: "取消",
      onOk: async () => {
        if (!projectId || !selectedFileId) return;
        setUsingNewVersion(true);
        try {
          await applyNewVersionUsingPost(projectId, selectedFileId);
          message.success("已使用新版本并清空标注");
          setFileVersionInfo(null);
          await loadTasks({ mode: "reset" });
          await initEditorForFile(selectedFileId);
        } catch (e) {
          console.error("使用新版本失败", e);
          message.error("使用新版本失败");
        } finally {
          setUsingNewVersion(false);
        }
      },
    });
  }, [modal, message, projectId, selectedFileId, fileVersionInfo, loadTasks, initEditorForFile]);

  const advanceAfterSave = useCallback(async (fileId: string, segmentIndex?: number) => {
    if (!fileId) return;
    if (segmented && segmentTotal > 0) {
      const baseIndex = Math.max(segmentIndex ?? currentSegmentIndex, 0);
      const nextSegmentIndex = baseIndex + 1;
      if (nextSegmentIndex < segmentTotal) {
        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,
    segmentTotal,
    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;

    const annotationRecord = annotation as Record<string, unknown>;
    const currentTask = tasks.find((item) => item.fileId === String(fileId));
    const currentStatus = currentTask?.annotationStatus;
    let resolvedStatus: AnnotationResultStatus;
    if (isAnnotationResultEmpty(annotationRecord)) {
      if (
        currentStatus === AnnotationResultStatus.NO_ANNOTATION ||
        currentStatus === AnnotationResultStatus.NOT_APPLICABLE
      ) {
        resolvedStatus = currentStatus;
      } else {
        const selectedStatus = await confirmEmptyAnnotationStatus();
        if (!selectedStatus) return false;
        resolvedStatus = selectedStatus;
      }
    } else {
      resolvedStatus = AnnotationResultStatus.ANNOTATED;
    }

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

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

      if (options?.autoAdvance) {
        await advanceAfterSave(String(fileId), segmentIndex);
      }
      return true;
    } catch (e) {
      console.error(e);
      message.error("保存失败");
      return false;
    } finally {
      setSaving(false);
    }
  }, [
    advanceAfterSave,
    confirmEmptyAnnotationStatus,
    currentSegmentIndex,
    message,
    projectId,
    segmented,
    selectedFileId,
    tasks,
  ]);

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

  useEffect(() => {
    const handleSaveShortcut = (event: KeyboardEvent) => {
      if (!isSaveShortcut(event) || event.repeat) return;
      if (saving || loadingTaskDetail) return;
      if (!iframeReady || !lsReady) return;
      event.preventDefault();
      event.stopPropagation();
      requestExport(false);
    };
    window.addEventListener("keydown", handleSaveShortcut);
    return () => window.removeEventListener("keydown", handleSaveShortcut);
  }, [iframeReady, loadingTaskDetail, lsReady, requestExport, 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);
    setCurrentSegmentIndex(0);
    setSegmentTotal(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]);

  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_SAVE_AND_NEXT") {
        pendingAutoAdvanceRef.current = false;
        saveFromExport(payload, { autoAdvance: true });
        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]);

  useEffect(() => {
    if (selectedFileId && project?.supported) {
      setFileVersionInfo(null);
      checkFileVersion(selectedFileId);
    }
  }, [selectedFileId, project?.supported, checkFileVersion]);

  const canLoadMore = taskTotalPages > 0 && taskPage + 1 < taskTotalPages;
  const saveDisabled =
    !iframeReady || !selectedFileId || saving || loadingTaskDetail;
  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="grid grid-cols-[1fr_auto_1fr] items-center 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 justify-center">
          {fileVersionInfo?.hasNewVersion && (
            <div className="flex items-center gap-2 mr-4">
              <Typography.Text type="warning" className="text-xs">
                ⚠ 文件有新版本（{fileVersionInfo.currentFileVersion}{" > "}{fileVersionInfo.annotationFileVersion}）
              </Typography.Text>
              <Button
                size="small"
                type="primary"
                danger
                loading={usingNewVersion}
                onClick={handleUseNewVersion}
              >
                使用新版本
              </Button>
            </div>
          )}
          {fileVersionInfo?.annotationVersionUnknown && !fileVersionInfo?.hasNewVersion && (
            <Typography.Text type="secondary" className="text-xs mr-4">
              历史标注缺少文件版本信息，保存后将绑定当前版本（{fileVersionInfo.currentFileVersion}）
            </Typography.Text>
          )}
          <Button
            type="primary"
            icon={<SaveOutlined />}
            loading={saving}
            disabled={saveDisabled}
            onClick={() => requestExport(true)}
          >
            {SAVE_AND_NEXT_LABEL}
          </Button>
        </div>
        <div className="flex items-center gap-2 justify-end">
          <Button
            icon={<ReloadOutlined />}
            loading={loadingTasks}
            onClick={() => loadTasks({ mode: "reset" })}
          >
            刷新
          </Button>
          <Button
            icon={<SaveOutlined />}
            loading={saving}
            disabled={saveDisabled}
            onClick={() => requestExport(false)}
          >
            保存
          </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 flex items-center justify-between gap-2">
            <span>文件列表</span>
          </div>
          <div className="flex-1 min-h-0 overflow-auto">
            <List
              loading={loadingTasks}
              size="small"
              dataSource={tasks}
              loadMore={loadMoreNode}
              renderItem={(item) => {
                const statusMeta = resolveTaskStatusMeta(item);
                return (
                  <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">
                          <div className="flex items-center gap-2">
                            <Typography.Text type={statusMeta.type} style={{ fontSize: 11 }}>
                              {statusMeta.text}
                            </Typography.Text>
                          </div>
                          {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 }}>
                  {segmentTotal > 0 ? currentSegmentIndex + 1 : 0} / {segmentTotal}
                </Tag>
              </div>
              <div className="flex-1 min-h-0 overflow-auto px-2 py-2">
                {segmentTotal > 0 ? (
                  <div className="grid grid-cols-[repeat(auto-fill,minmax(44px,1fr))] gap-1">
                    {segmentIndices.map((segmentIndex) => {
                      const isCurrent = segmentIndex === currentSegmentIndex;
                      return (
                        <div
                          key={segmentIndex}
                          className={
                            isCurrent
                              ? "h-7 leading-7 rounded bg-blue-500 text-white text-center text-xs font-medium"
                              : "h-7 leading-7 rounded bg-gray-100 text-gray-700 text-center text-xs"
                          }
                        >
                          {segmentIndex + 1}
                        </div>
                      );
                    })}
                  </div>
                ) : (
                  <div className="py-6">
                    <Empty
                      image={Empty.PRESENTED_IMAGE_SIMPLE}
                      description="暂无分段"
                    />
                  </div>
                )}
              </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>
  );
}
