"use client";

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type {
  DocumentEditorHostProps,
  EditorHostEvent,
  EditorHostFallbackReason,
} from "@/components/editor/editor-host-types";
import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
import { useEditorBridgeStore } from "@/store/editor-bridge";
import type { EditorReferenceBridge } from "@/store/editor-bridge";
import type { DocumentStats } from "@/types/page-options";
import type { Json } from "@/types/supabase";
import {
  blocksFromTiptapDoc,
  editorBlockDocumentFromTiptapDoc,
  tiptapDocFromBlocks,
} from "@/lib/documents/tiptap-content-converter";
import { buildDocumentSavePayload } from "@/lib/documents/save-contract";
import { executePageBodySavePayload } from "@/lib/documents/page-command-client";
import { loadLeptosTiptapRuntime } from "@/components/editor/leptos-tiptap-runtime-loader";

type RuntimeSelectionState = Record<string, boolean | number | null | undefined> & {
  current_block_index?: number | null;
  current_block_from?: number | null;
  current_block_to?: number | null;
  selection_from?: number | null;
  selection_to?: number | null;
  selection_empty?: boolean;
};

type RuntimeSnapshot = {
  tiptapDocument: unknown;
  blocks: Json;
  stats: DocumentStats;
};

type TiptapMark = {
  type?: string;
  attrs?: Record<string, unknown>;
};

type TiptapNode = {
  type?: string;
  attrs?: Record<string, unknown>;
  content?: TiptapNode[];
  text?: string;
  marks?: TiptapMark[];
};

type TiptapDoc = {
  type: "doc";
  content: TiptapNode[];
};

type FloatingAnchor = {
  top: number;
  left: number;
};

type HoveredBlockState = {
  index: number;
  label: string;
  top: number;
  height: number;
};

type SlashActionId =
  | "paragraph"
  | "heading1"
  | "heading2"
  | "heading3"
  | "bullet_list"
  | "ordered_list"
  | "todo"
  | "quote"
  | "code_block";

type SlashAction = {
  id: SlashActionId;
  label: string;
  description: string;
};

const INLINE_RUNTIME_STATUS = {
  booting: "booting",
  ready: "ready",
  dirty: "dirty",
  saving: "saving",
  saved: "saved",
  error: "error",
} as const;

const EMPTY_DOC: TiptapDoc = {
  type: "doc",
  content: [{ type: "paragraph", attrs: {}, content: [] }],
};

const SLASH_ACTIONS: SlashAction[] = [
  { id: "paragraph", label: "段落", description: "恢复普通段落" },
  { id: "heading1", label: "一级标题", description: "用于主标题" },
  { id: "heading2", label: "二级标题", description: "用于章节标题" },
  { id: "heading3", label: "三级标题", description: "用于子章节标题" },
  { id: "bullet_list", label: "无序列表", description: "项目符号列表" },
  { id: "ordered_list", label: "有序列表", description: "编号列表" },
  { id: "todo", label: "待办列表", description: "taskList / taskItem" },
  { id: "quote", label: "引用块", description: "强调引用内容" },
  { id: "code_block", label: "代码块", description: "保留代码文本" },
];

function nowIso(): string {
  return new Date().toISOString();
}

function flattenText(value: unknown): string {
  if (typeof value === "string") return value;
  if (Array.isArray(value)) return value.map(flattenText).join("");
  if (value && typeof value === "object") {
    const record = value as { text?: unknown; content?: unknown };
    return `${flattenText(record.text)}${flattenText(record.content)}`;
  }
  return "";
}

function buildStats(blocks: Json): DocumentStats {
  const items = Array.isArray(blocks) ? blocks : [];
  const text = items
    .map((item) =>
      item && typeof item === "object"
        ? flattenText((item as { content?: unknown }).content)
        : "",
    )
    .join("\n")
    .trim();
  const todoItems = items.filter(
    (item) =>
      item && typeof item === "object" && (item as { type?: unknown }).type === "todo",
  );
  const todoDone = todoItems.filter(
    (item) =>
      item &&
      typeof item === "object" &&
      Boolean((item as { props?: { checked?: unknown } }).props?.checked),
  ).length;

  return {
    wordCount: text ? text.split(/\s+/).filter(Boolean).length : 0,
    characterCount: text.length,
    blockCount: items.length,
    todoTotal: todoItems.length,
    todoDone,
  };
}

function normalizeMountId(documentId: string): string {
  return `leptos-tiptap-inline-${documentId.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
}

function readSelectionInteger(value: unknown): number | null {
  return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null;
}

function readBlockId(value: unknown): string | null {
  if (!value || typeof value !== "object") {
    return null;
  }
  const record = value as { id?: unknown; blockId?: unknown };
  const candidate =
    typeof record.id === "string" && record.id.trim()
      ? record.id
      : typeof record.blockId === "string" && record.blockId.trim()
        ? record.blockId
        : null;
  return candidate ? candidate.trim() : null;
}

function randomSuffix(): string {
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
    return crypto.randomUUID();
  }
  return `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
}

function createBlockId(): string {
  return `block-${randomSuffix()}`;
}

function cloneJson<T>(value: T): T {
  return JSON.parse(JSON.stringify(value)) as T;
}

function normalizeTiptapDoc(doc: unknown): TiptapDoc {
  if (!doc || typeof doc !== "object") {
    return cloneJson(EMPTY_DOC);
  }
  const maybeDoc = doc as { type?: unknown; content?: unknown };
  if (maybeDoc.type !== "doc" || !Array.isArray(maybeDoc.content)) {
    return cloneJson(EMPTY_DOC);
  }
  return cloneJson(maybeDoc as TiptapDoc);
}

function snapshotFromTiptapDocument(tiptapDocument: unknown): RuntimeSnapshot {
  const blocks = blocksFromTiptapDoc(tiptapDocument) as Json;
  return {
    tiptapDocument,
    blocks,
    stats: buildStats(blocks),
  };
}

function normalizeEventTargetNode(target: EventTarget | Node | null): Node | null {
  if (!target) return null;
  if (target instanceof Node) return target;
  return null;
}

function normalizeEventTargetElement(
  target: EventTarget | Node | null,
): Element | null {
  const node = normalizeEventTargetNode(target);
  if (!node) return null;
  if (node.nodeType === Node.ELEMENT_NODE) {
    return node as Element;
  }
  return node.parentElement;
}

function containsNode(root: Element, node: Node | null): boolean {
  if (!node) return false;
  if (node.nodeType === Node.ELEMENT_NODE) {
    return root.contains(node);
  }
  return root.contains(node.parentElement);
}

function extractInlineContent(node: TiptapNode | undefined): TiptapNode[] {
  if (!node) return [];
  if (node.type === "text") {
    return typeof node.text === "string"
      ? [{ type: "text", text: node.text, marks: node.marks }]
      : [];
  }
  if (!Array.isArray(node.content)) {
    return [];
  }
  switch (node.type) {
    case "paragraph":
    case "heading":
      return node.content.flatMap((child) => extractInlineContent(child));
    case "blockquote":
      return extractInlineContent(node.content[0]);
    case "bulletList":
    case "orderedList":
    case "taskList":
      return extractInlineContent(node.content[0]?.content?.[0]);
    case "codeBlock": {
      const text = collectPlainText(node);
      return text ? [{ type: "text", text }] : [];
    }
    default:
      return node.content.flatMap((child) => extractInlineContent(child));
  }
}

function collectPlainText(node: TiptapNode | undefined): string {
  if (!node) return "";
  if (node.type === "text") {
    return typeof node.text === "string" ? node.text : "";
  }
  if (!Array.isArray(node.content)) {
    return "";
  }
  return node.content.map((child) => collectPlainText(child)).join("");
}

function createAttrsWithBlockId(
  currentAttrs: Record<string, unknown> | undefined,
  blockId: string,
): Record<string, unknown> {
  return { ...(currentAttrs ?? {}), blockId };
}

function createParagraphNode(
  blockId: string,
  content: TiptapNode[] = [],
): TiptapNode {
  return {
    type: "paragraph",
    attrs: createAttrsWithBlockId(undefined, blockId),
    content,
  };
}

function createListItemNode(blockId: string, content: TiptapNode[]): TiptapNode {
  return {
    type: "listItem",
    attrs: createAttrsWithBlockId(undefined, blockId),
    content: [createParagraphNode(blockId, content)],
  };
}

function createTaskItemNode(blockId: string, content: TiptapNode[]): TiptapNode {
  return {
    type: "taskItem",
    attrs: { ...createAttrsWithBlockId(undefined, blockId), checked: false },
    content: [createParagraphNode(blockId, content)],
  };
}

function topLevelBlockLabel(node: TiptapNode | undefined): string {
  switch (node?.type) {
    case "heading":
      return `标题 ${String(node.attrs?.level ?? "") || ""}`.trim();
    case "bulletList":
      return "无序列表";
    case "orderedList":
      return "有序列表";
    case "taskList":
      return "待办列表";
    case "blockquote":
      return "引用块";
    case "codeBlock":
      return "代码块";
    case "horizontalRule":
      return "分割线";
    case "paragraph":
    default:
      return "段落";
  }
}

function replaceBlockIdRecursively(node: TiptapNode, blockId: string): TiptapNode {
  const nextNode: TiptapNode = {
    ...node,
    attrs: createAttrsWithBlockId(node.attrs, blockId),
    content: Array.isArray(node.content)
      ? node.content.map((child) => replaceBlockIdRecursively(child, blockId))
      : node.content,
  };
  if (node.type === "taskItem") {
    nextNode.attrs = {
      ...nextNode.attrs,
      checked: Boolean(node.attrs?.checked),
    };
  }
  return nextNode;
}

function turnBlockInto(node: TiptapNode, action: SlashActionId): TiptapNode {
  const blockId = readBlockId(node.attrs) ?? createBlockId();
  const inline = extractInlineContent(node);
  switch (action) {
    case "paragraph":
      return createParagraphNode(blockId, inline);
    case "heading1":
      return {
        type: "heading",
        attrs: { ...createAttrsWithBlockId(node.attrs, blockId), level: 1 },
        content: inline,
      };
    case "heading2":
      return {
        type: "heading",
        attrs: { ...createAttrsWithBlockId(node.attrs, blockId), level: 2 },
        content: inline,
      };
    case "heading3":
      return {
        type: "heading",
        attrs: { ...createAttrsWithBlockId(node.attrs, blockId), level: 3 },
        content: inline,
      };
    case "bullet_list":
      return {
        type: "bulletList",
        attrs: createAttrsWithBlockId(node.attrs, blockId),
        content: [createListItemNode(blockId, inline)],
      };
    case "ordered_list":
      return {
        type: "orderedList",
        attrs: createAttrsWithBlockId(node.attrs, blockId),
        content: [createListItemNode(blockId, inline)],
      };
    case "todo":
      return {
        type: "taskList",
        attrs: createAttrsWithBlockId(node.attrs, blockId),
        content: [createTaskItemNode(blockId, inline)],
      };
    case "quote":
      return {
        type: "blockquote",
        attrs: createAttrsWithBlockId(node.attrs, blockId),
        content: [createParagraphNode(blockId, inline)],
      };
    case "code_block": {
      const text = collectPlainText(node);
      return {
        type: "codeBlock",
        attrs: {
          ...createAttrsWithBlockId(node.attrs, blockId),
          language:
            typeof node.attrs?.language === "string" && node.attrs.language
              ? node.attrs.language
              : "text",
        },
        content: text ? [{ type: "text", text }] : [],
      };
    }
    default:
      return createParagraphNode(blockId, inline);
  }
}

function blockAnchorFromElement(
  host: HTMLElement,
  block: Element,
): { top: number; left: number; height: number } {
  const hostRect = host.getBoundingClientRect();
  const blockRect = block.getBoundingClientRect();
  return {
    top: blockRect.top - hostRect.top,
    left: blockRect.left - hostRect.left,
    height: blockRect.height,
  };
}

export function LeptosTiptapRuntimeEditorHost(props: DocumentEditorHostProps) {
  const registerBridge = useEditorBridgeStore((state) => state.registerBridge);
  const mountId = useMemo(() => normalizeMountId(props.documentId), [props.documentId]);
  const shellRef = useRef<HTMLDivElement | null>(null);
  const generationRef = useRef<number | null>(null);
  const readyRef = useRef(false);
  const initialContentRef = useRef(props.initialContent);
  const onSnapshotRef = useRef(props.onSnapshot);
  const onStatsChangeRef = useRef(props.onStatsChange);
  const onPersistedMetaChangeRef = useRef(props.onPersistedMetaChange);
  const onHostEventRef = useRef(props.onHostEvent);
  const onRequestFallbackRef = useRef(props.onRequestFallback);
  const selectionStateRef = useRef<RuntimeSelectionState>({});
  const runtimeLoadedRef = useRef(false);
  const revisionRef = useRef<number | null>(props.initialRevision ?? null);
  const conflictDetectionKeyRef = useRef<string | null>(
    props.initialConflictDetectionKey ?? null,
  );
  const latestSnapshotRef = useRef<RuntimeSnapshot | null>(null);
  const [status, setStatus] = useState<string>(INLINE_RUNTIME_STATUS.booting);
  const [errorMessage, setErrorMessage] = useState<string | null>(null);
  const [selectionState, setSelectionState] = useState<RuntimeSelectionState>({});
  const [toolbarAnchor, setToolbarAnchor] = useState<FloatingAnchor | null>(null);
  const [slashIndex, setSlashIndex] = useState(0);
  const [slashMenu, setSlashMenu] = useState<{
    anchor: FloatingAnchor;
    blockIndex: number;
  } | null>(null);
  const [hoveredBlock, setHoveredBlock] = useState<HoveredBlockState | null>(null);
  const [blockMenuBlock, setBlockMenuBlock] = useState<HoveredBlockState | null>(null);
  const [colorMenuOpen, setColorMenuOpen] = useState(false);
  const [currentBlockLabel, setCurrentBlockLabel] = useState("未定位");

  const getEditorRoot = useCallback((): HTMLElement | null => {
    const host = document.getElementById(mountId);
    const root = host?.querySelector(".ProseMirror");
    return root instanceof HTMLElement ? root : null;
  }, [mountId]);

  useEffect(() => {
    onSnapshotRef.current = props.onSnapshot;
    onStatsChangeRef.current = props.onStatsChange;
    onPersistedMetaChangeRef.current = props.onPersistedMetaChange;
    onHostEventRef.current = props.onHostEvent;
    onRequestFallbackRef.current = props.onRequestFallback;
    initialContentRef.current = props.initialContent;
    revisionRef.current = props.initialRevision ?? null;
    conflictDetectionKeyRef.current = props.initialConflictDetectionKey ?? null;
  }, [
    props.initialContent,
    props.initialConflictDetectionKey,
    props.initialRevision,
    props.onHostEvent,
    props.onPersistedMetaChange,
    props.onRequestFallback,
    props.onSnapshot,
    props.onStatsChange,
  ]);

  const emitHostEvent = useCallback((event: EditorHostEvent) => {
    onHostEventRef.current?.(event);
  }, []);

  const emitStatus = useCallback(
    (nextStatus: string, message?: string | null) => {
      const at = nowIso();
      setStatus(nextStatus);
      if (nextStatus !== INLINE_RUNTIME_STATUS.error) {
        setErrorMessage(message ?? null);
      } else if (message !== undefined) {
        setErrorMessage(message);
      }
      emitHostEvent({
        kind: "status_changed",
        status: nextStatus,
        at,
        message: message ?? null,
      });
    },
    [emitHostEvent],
  );

  const emitFailure = useCallback(
    (
      reason: Exclude<EditorHostFallbackReason, "explicit_fallback">,
      message: string,
      options?: { requestFallback?: boolean },
    ) => {
      const at = nowIso();
      setStatus(INLINE_RUNTIME_STATUS.error);
      setErrorMessage(message);
      emitHostEvent({
        kind: reason,
        at,
        message,
      });
      if (options?.requestFallback) {
        emitHostEvent({
          kind: "fallback_triggered",
          at,
          reason,
          message,
        });
        onRequestFallbackRef.current?.({
          reason,
          error: message,
          at,
        });
      }
    },
    [emitHostEvent],
  );

  const requestFallbackToBlockNote = useCallback(
    (message?: string | null) => {
      const at = nowIso();
      emitHostEvent({
        kind: "fallback_triggered",
        at,
        reason: "explicit_fallback",
        message: message ?? "切换到 BlockNote fallback",
      });
      onRequestFallbackRef.current?.({
        reason: "explicit_fallback",
        error: message ?? null,
        at,
      });
    },
    [emitHostEvent],
  );

  const updateLocalSnapshot = useCallback((tiptapDocument: unknown) => {
    const snapshot = snapshotFromTiptapDocument(tiptapDocument);
    latestSnapshotRef.current = snapshot;
    onSnapshotRef.current?.({
      blocks: snapshot.blocks,
      stats: snapshot.stats,
    });
    onStatsChangeRef.current?.(snapshot.stats);
    return snapshot;
  }, []);

  const readCurrentSnapshot = useCallback(async (): Promise<RuntimeSnapshot> => {
    const generation = generationRef.current;
    if (!runtimeLoadedRef.current || generation == null) {
      throw new Error("runtime host 尚未准备就绪");
    }
    const runtime = await loadLeptosTiptapRuntime();
    const response = runtime.bridge.document({
      id: mountId,
      generation,
      request: {
        kind: "get_content",
        format: "json",
      },
    });
    if (!response.ok) {
      throw new Error(response.error.message);
    }
    return updateLocalSnapshot(response.value.content.value);
  }, [mountId, updateLocalSnapshot]);

  const getCurrentDoc = useCallback(async (): Promise<TiptapDoc> => {
    const snapshot = latestSnapshotRef.current ?? (await readCurrentSnapshot());
    return normalizeTiptapDoc(snapshot.tiptapDocument);
  }, [readCurrentSnapshot]);

  const publishSnapshot = useCallback(async () => {
    const snapshot = await readCurrentSnapshot();
    return snapshot;
  }, [readCurrentSnapshot]);

  const persistSnapshot = async () => {
    emitStatus(INLINE_RUNTIME_STATUS.saving);
    const snapshot = latestSnapshotRef.current ?? (await readCurrentSnapshot());
    const persistedMeta = await executePageBodySavePayload(
      buildDocumentSavePayload({
        documentId: props.documentId,
        workspaceId: props.workspaceId,
        revision: revisionRef.current,
        editorDocument: editorBlockDocumentFromTiptapDoc(
          snapshot.tiptapDocument,
          props.documentId,
        ),
        content: snapshot.blocks,
        tiptapDocument: snapshot.tiptapDocument,
        conflictDetectionKey: conflictDetectionKeyRef.current,
        snapshotCapturedAt: nowIso(),
        blockCount: snapshot.stats.blockCount,
      }),
    );
    revisionRef.current = persistedMeta.revision ?? revisionRef.current;
    conflictDetectionKeyRef.current =
      persistedMeta.conflictDetectionKey ?? conflictDetectionKeyRef.current;
    onPersistedMetaChangeRef.current?.({
      revision: revisionRef.current,
      conflictDetectionKey: conflictDetectionKeyRef.current,
    });
    emitStatus(INLINE_RUNTIME_STATUS.saved);
  };

  const syncTiptapDocumentToRuntime = useCallback(
    async (tiptapDocument: TiptapDoc, options?: { emitUpdate?: boolean }) => {
      const generation = generationRef.current;
      if (!runtimeLoadedRef.current || generation == null) {
        return;
      }
      const runtime = await loadLeptosTiptapRuntime();
      const response = runtime.bridge.document({
        id: mountId,
        generation,
        request: {
          kind: "set_content",
          content: {
            format: "json",
            value: tiptapDocument,
          },
          options: {
            emit_update: Boolean(options?.emitUpdate),
          },
        },
      });
      if (!response.ok) {
        throw new Error(response.error.message);
      }
      updateLocalSnapshot(tiptapDocument);
    },
    [mountId, updateLocalSnapshot],
  );

  const syncExternalBlocksToRuntime = useCallback(
    async (blocks: Json, options?: { emitUpdate?: boolean }) => {
      const nextDocument = tiptapDocFromBlocks(blocks);
      await syncTiptapDocumentToRuntime(nextDocument, options);
    },
    [syncTiptapDocumentToRuntime],
  );

  const debouncedPersist = useDebouncedCallback(() => {
    void persistSnapshot().catch((error) => {
      emitFailure(
        "save_failed",
        error instanceof Error ? error.message : "保存失败",
      );
    });
  }, 800);

  const executeRuntimeCommand = useCallback(
    async (command: unknown) => {
      const generation = generationRef.current;
      if (!runtimeLoadedRef.current || generation == null) {
      throw new Error("runtime host 尚未准备就绪");
      }
      const runtime = await loadLeptosTiptapRuntime();
      const response = runtime.bridge.command({
        id: mountId,
        generation,
        command,
      });
      if (!response.ok) {
        throw new Error(response.error.message);
      }
    },
    [mountId],
  );

  const resolveCurrentBlockIndex = useCallback(() => {
    return readSelectionInteger(selectionStateRef.current.current_block_index);
  }, []);

  const resolveCurrentBlockRange = useCallback(() => {
    const from = readSelectionInteger(selectionStateRef.current.current_block_from);
    const to = readSelectionInteger(selectionStateRef.current.current_block_to);
    if (from == null || to == null || to < from) {
      return null;
    }
    return { from, to };
  }, []);

  const resolveCurrentBlockId = useCallback(() => {
    const index = resolveCurrentBlockIndex();
    if (index == null) {
      return null;
    }
    const blocks = latestSnapshotRef.current?.blocks;
    if (!Array.isArray(blocks)) {
      return null;
    }
    return readBlockId(blocks[index]);
  }, [resolveCurrentBlockIndex]);

  const applyTopLevelDocumentMutation = useCallback(
    async (mutate: (doc: TiptapDoc) => boolean | Promise<boolean>) => {
      const currentDoc = await getCurrentDoc();
      const nextDoc = cloneJson(currentDoc);
      const changed = await mutate(nextDoc);
      if (!changed) {
        return;
      }
      await syncTiptapDocumentToRuntime(nextDoc, { emitUpdate: true });
      emitStatus(INLINE_RUNTIME_STATUS.dirty);
      debouncedPersist();
    },
    [debouncedPersist, emitStatus, getCurrentDoc, syncTiptapDocumentToRuntime],
  );

  const runSlashOrTurnIntoAction = useCallback(
    async (blockIndex: number, action: SlashActionId) => {
      await applyTopLevelDocumentMutation((doc) => {
        if (!Array.isArray(doc.content) || blockIndex < 0 || blockIndex >= doc.content.length) {
          return false;
        }
        const currentNode = doc.content[blockIndex];
        doc.content[blockIndex] = turnBlockInto(currentNode, action);
        return true;
      });
      await executeRuntimeCommand({ kind: "focus" });
      await executeRuntimeCommand({ kind: "select_textblock_end" });
    },
    [applyTopLevelDocumentMutation, executeRuntimeCommand],
  );

  const duplicateBlockAt = useCallback(
    async (blockIndex: number) => {
      await applyTopLevelDocumentMutation((doc) => {
        if (!Array.isArray(doc.content) || blockIndex < 0 || blockIndex >= doc.content.length) {
          return false;
        }
        const duplicated = replaceBlockIdRecursively(
          cloneJson(doc.content[blockIndex]),
          createBlockId(),
        );
        doc.content.splice(blockIndex + 1, 0, duplicated);
        return true;
      });
      await executeRuntimeCommand({ kind: "focus" });
    },
    [applyTopLevelDocumentMutation, executeRuntimeCommand],
  );

  const deleteBlockAt = useCallback(
    async (blockIndex: number) => {
      await applyTopLevelDocumentMutation((doc) => {
        if (!Array.isArray(doc.content) || blockIndex < 0 || blockIndex >= doc.content.length) {
          return false;
        }
        if (doc.content.length === 1) {
          doc.content[0] = createParagraphNode(createBlockId(), []);
          return true;
        }
        doc.content.splice(blockIndex, 1);
        return true;
      });
      await executeRuntimeCommand({ kind: "focus" });
    },
    [applyTopLevelDocumentMutation, executeRuntimeCommand],
  );

  const insertParagraphAfter = useCallback(
    async (blockIndex: number) => {
      await applyTopLevelDocumentMutation((doc) => {
        if (!Array.isArray(doc.content) || blockIndex < 0 || blockIndex >= doc.content.length) {
          return false;
        }
        doc.content.splice(blockIndex + 1, 0, createParagraphNode(createBlockId(), []));
        return true;
      });
      await executeRuntimeCommand({ kind: "focus" });
      await executeRuntimeCommand({ kind: "select_textblock_end" });
    },
    [applyTopLevelDocumentMutation, executeRuntimeCommand],
  );

  const readSelectionAnchor = useCallback((): FloatingAnchor | null => {
    const host = shellRef.current;
    const root = getEditorRoot();
    if (!host || !root) {
      return null;
    }
    const selection = window.getSelection();
    if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
      return null;
    }
    const anchorInside = containsNode(root, selection.anchorNode);
    const focusInside = containsNode(root, selection.focusNode);
    if (!anchorInside && !focusInside) {
      return null;
    }
    const range = selection.getRangeAt(0);
    const rect = range.getBoundingClientRect();
    if (rect.width <= 0 && rect.height <= 0) {
      return null;
    }
    const hostRect = host.getBoundingClientRect();
    const left = rect.left + rect.width / 2 - hostRect.left;
    const top = Math.max(rect.top - hostRect.top - 54, 12);
    return {
      top,
      left,
    };
  }, [getEditorRoot]);

  const readBlockAnchorByIndex = useCallback(
    (blockIndex: number): FloatingAnchor | null => {
      const host = shellRef.current;
      const root = getEditorRoot();
      if (!host || !root || blockIndex < 0) {
        return null;
      }
      const block = root.children.item(blockIndex);
      if (!(block instanceof Element)) {
        return null;
      }
      const anchor = blockAnchorFromElement(host, block);
      return {
        top: Math.max(anchor.top + Math.min(anchor.height / 2, 24), 12),
        left: Math.max(anchor.left + 120, 96),
      };
    },
    [getEditorRoot],
  );

  const updateToolbarAnchor = useCallback(() => {
    if (slashMenu || blockMenuBlock) {
      setToolbarAnchor(null);
      return;
    }
    setToolbarAnchor(readSelectionAnchor());
  }, [blockMenuBlock, readSelectionAnchor, slashMenu]);

  const updateHoveredBlockFromTarget = useCallback(
    (target: EventTarget | Node | null) => {
      const host = shellRef.current;
      const root = getEditorRoot();
      const element = normalizeEventTargetElement(target);
      if (!host || !root || !element || !root.contains(element)) {
        setHoveredBlock(null);
        return;
      }
      let block: Element | null = element;
      while (block && block.parentElement !== root) {
        block = block.parentElement;
      }
      if (!block) {
        setHoveredBlock(null);
        return;
      }
      const blockIndex = Array.from(root.children).findIndex((child) => child === block);
      if (blockIndex < 0) {
        setHoveredBlock(null);
        return;
      }
      const currentDoc = latestSnapshotRef.current?.tiptapDocument;
      const node = normalizeTiptapDoc(currentDoc).content[blockIndex];
      const anchor = blockAnchorFromElement(host, block);
      setHoveredBlock({
        index: blockIndex,
        label: topLevelBlockLabel(node),
        top: anchor.top,
        height: anchor.height,
      });
    },
    [getEditorRoot],
  );

  const openSlashMenu = useCallback(() => {
    const blockIndex = resolveCurrentBlockIndex();
    if (blockIndex == null) {
      return;
    }
    const anchor = readSelectionAnchor() ?? readBlockAnchorByIndex(blockIndex);
    if (!anchor) {
      return;
    }
    setSlashIndex(0);
    setBlockMenuBlock(null);
    setColorMenuOpen(false);
    setSlashMenu({
      anchor,
      blockIndex,
    });
    setToolbarAnchor(null);
  }, [readBlockAnchorByIndex, readSelectionAnchor, resolveCurrentBlockIndex]);

  useEffect(() => {
    let disposed = false;
    readyRef.current = false;
    runtimeLoadedRef.current = false;
    generationRef.current = null;
    selectionStateRef.current = {};
    latestSnapshotRef.current = null;
    setSelectionState({});
    setToolbarAnchor(null);
    setSlashMenu(null);
    setBlockMenuBlock(null);
    setHoveredBlock(null);
    setColorMenuOpen(false);
    queueMicrotask(() => {
      if (disposed) return;
      emitStatus(INLINE_RUNTIME_STATUS.booting);
    });

    const initialDocument = tiptapDocFromBlocks(initialContentRef.current as Json);

    void loadLeptosTiptapRuntime()
      .then((runtime) => {
        if (disposed) return;
        runtime.bridge.create(
          {
            id: mountId,
            content: {
              format: "json",
              value: initialDocument,
            },
            editable: !props.readOnly,
            placeholder: "输入 '/' 打开命令菜单，AI 将直接进入正文命令链",
            extensions: [
              "blockquote",
              "bold",
              "bullet_list",
              "code",
              "code_block",
              "document",
              "dropcursor",
              "gapcursor",
              "hard_break",
              "heading",
              "highlight",
              "history",
              "horizontal_rule",
              "italic",
              "link",
              "list_item",
              "ordered_list",
              "paragraph",
              "placeholder",
              "strike",
              "task_item",
              "task_list",
              "text",
              "text_align",
              "text_style",
              "underline",
            ],
          },
          ({ generation }) => {
            if (disposed) return;
            generationRef.current = generation;
            readyRef.current = true;
            runtimeLoadedRef.current = true;
            emitStatus(INLINE_RUNTIME_STATUS.ready);
            void publishSnapshot().catch((error) => {
              emitFailure(
                "command_failed",
                error instanceof Error ? error.message : "读取 inline editor 快照失败",
              );
            });
          },
          () => {
            if (disposed) return;
            emitStatus(INLINE_RUNTIME_STATUS.dirty);
            void publishSnapshot().catch((error) => {
              emitFailure(
                "command_failed",
                error instanceof Error ? error.message : "读取 inline editor 快照失败",
              );
            });
            debouncedPersist();
          },
          (selection) => {
            const nextSelection = selection as RuntimeSelectionState;
            selectionStateRef.current = nextSelection;
            setSelectionState(nextSelection);
          },
          (error) => {
            if (disposed) return;
            const message = error.message || "runtime host 初始化失败";
            emitFailure(
              readyRef.current ? "command_failed" : "host_init_failed",
              message,
              { requestFallback: !readyRef.current },
            );
          },
        );
      })
      .catch((error) => {
        if (disposed) return;
        emitFailure(
          "runtime_load_failed",
          error instanceof Error ? error.message : "加载 leptos-tiptap runtime 失败",
          { requestFallback: true },
        );
      });

    return () => {
      disposed = true;
      debouncedPersist.cancel();
      if (runtimeLoadedRef.current) {
        void loadLeptosTiptapRuntime()
          .then((runtime) => runtime.bridge.destroy(mountId))
          .catch(() => {
            // 说明：销毁失败不影响页面离开流程。
          });
      }
      readyRef.current = false;
      runtimeLoadedRef.current = false;
      generationRef.current = null;
      registerBridge(null);
    };
  }, [
    debouncedPersist,
    emitFailure,
    emitStatus,
    mountId,
    props.readOnly,
    publishSnapshot,
    registerBridge,
  ]);

  useEffect(() => {
    const currentBlocks = latestSnapshotRef.current?.blocks;
    if (currentBlocks === props.initialContent) {
      return;
    }
    if (!runtimeLoadedRef.current || generationRef.current == null) {
      return;
    }
    void syncExternalBlocksToRuntime(props.initialContent as Json).catch((error) => {
      emitFailure(
        "command_failed",
        error instanceof Error ? error.message : "同步 inline editor 外部内容失败",
      );
    });
  }, [emitFailure, props.initialContent, syncExternalBlocksToRuntime]);

  useEffect(() => {
    if (!runtimeLoadedRef.current || generationRef.current == null) {
      return;
    }
    void executeRuntimeCommand({
      kind: "set_editable",
      editable: !props.readOnly,
    }).catch((error) => {
      emitFailure(
        "command_failed",
        error instanceof Error ? error.message : "更新编辑状态失败",
      );
    });
  }, [emitFailure, executeRuntimeCommand, props.readOnly]);

  useEffect(() => {
    updateToolbarAnchor();
  }, [selectionState, updateToolbarAnchor]);

  useEffect(() => {
    const blockIndex = readSelectionInteger(selectionState.current_block_index);
    const currentDoc = normalizeTiptapDoc(latestSnapshotRef.current?.tiptapDocument);
    const nextLabel =
      blockIndex != null && blockIndex >= 0 && blockIndex < currentDoc.content.length
        ? topLevelBlockLabel(currentDoc.content[blockIndex])
        : "未定位";
    setCurrentBlockLabel(nextLabel);
  }, [selectionState]);

  useEffect(() => {
    const onMouseUp = () => updateToolbarAnchor();
    const onSelectionChange = () => updateToolbarAnchor();
    const onKeyUp = () => updateToolbarAnchor();
    window.addEventListener("mouseup", onMouseUp);
    document.addEventListener("selectionchange", onSelectionChange);
    window.addEventListener("keyup", onKeyUp);
    return () => {
      window.removeEventListener("mouseup", onMouseUp);
      document.removeEventListener("selectionchange", onSelectionChange);
      window.removeEventListener("keyup", onKeyUp);
    };
  }, [updateToolbarAnchor]);

  useEffect(() => {
    const handlePointerDown = (event: MouseEvent) => {
      const target = normalizeEventTargetElement(event.target);
      if (!target) {
        setSlashMenu(null);
        setBlockMenuBlock(null);
        setColorMenuOpen(false);
        return;
      }
      if (
        target.closest("[data-inline-floating-menu='true']") ||
        target.closest("[data-inline-block-handle='true']")
      ) {
        return;
      }
      if (!shellRef.current?.contains(target)) {
        setSlashMenu(null);
        setBlockMenuBlock(null);
        setColorMenuOpen(false);
        setToolbarAnchor(null);
      }
    };
    window.addEventListener("mousedown", handlePointerDown);
    return () => window.removeEventListener("mousedown", handlePointerDown);
  }, []);

  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      const root = getEditorRoot();
      if (!root) {
        return;
      }
      const activeElement =
        document.activeElement && root.contains(document.activeElement)
          ? document.activeElement
          : null;
      const selection = window.getSelection();
      const selectionInside =
        !!selection &&
        (containsNode(root, selection.anchorNode) || containsNode(root, selection.focusNode));
      const editorActive = Boolean(activeElement) || selectionInside;
      if (!editorActive) {
        return;
      }

      if (slashMenu) {
        if (event.key === "ArrowDown") {
          event.preventDefault();
          setSlashIndex((value) => (value + 1) % SLASH_ACTIONS.length);
          return;
        }
        if (event.key === "ArrowUp") {
          event.preventDefault();
          setSlashIndex((value) => (value === 0 ? SLASH_ACTIONS.length - 1 : value - 1));
          return;
        }
        if (event.key === "Enter") {
          event.preventDefault();
          const action = SLASH_ACTIONS[slashIndex];
          void runSlashOrTurnIntoAction(slashMenu.blockIndex, action.id)
            .then(() => {
              setSlashMenu(null);
            })
            .catch((error) => {
              emitFailure(
                "command_failed",
                error instanceof Error ? error.message : "执行 slash 命令失败",
              );
            });
          return;
        }
        if (event.key === "Escape") {
          event.preventDefault();
          setSlashMenu(null);
          return;
        }
      }

      if (event.key === "Escape") {
        setSlashMenu(null);
        setBlockMenuBlock(null);
        setColorMenuOpen(false);
        return;
      }

      if (
        event.key === "/" &&
        !event.altKey &&
        !event.ctrlKey &&
        !event.metaKey &&
        !event.shiftKey
      ) {
        event.preventDefault();
        openSlashMenu();
      }
    };

    window.addEventListener("keydown", handleKeyDown, true);
    return () => window.removeEventListener("keydown", handleKeyDown, true);
  }, [
    emitFailure,
    getEditorRoot,
    openSlashMenu,
    runSlashOrTurnIntoAction,
    slashIndex,
    slashMenu,
  ]);

  useEffect(() => {
    const bridge: EditorReferenceBridge = {
      insertInlineReference: (target, aliasText) => {
        const blockId = resolveCurrentBlockId();
        const label = aliasText?.trim() || target.title || "无标题";
        const href = `/documents/${target.id}`;
        void (async () => {
          await executeRuntimeCommand({ kind: "focus" });
          await executeRuntimeCommand({
            kind: "insert_content",
            content: {
              format: "json",
              value: [
                {
                  type: "text",
                  text: label,
                  marks: [{ type: "link", attrs: { href } }],
                },
                {
                  type: "text",
                  text: " ",
                },
              ],
            },
          });
        })().catch((error) => {
          emitFailure(
            "command_failed",
            error instanceof Error ? error.message : "插入行内引用失败",
          );
        });
        return { blockId };
      },
      insertEmbedReference: (target) => {
        const blockId = resolveCurrentBlockId();
        const blockRange = resolveCurrentBlockRange();
        const href = `/documents/${target.id}`;
        const insertCommand = blockRange
          ? {
              kind: "insert_content_at" as const,
              position: blockRange.to + 1,
              content: {
                format: "json" as const,
                value: {
                  type: "paragraph",
                  content: [
                    {
                      type: "text",
                      text: target.title || "无标题",
                      marks: [{ type: "link", attrs: { href } }],
                    },
                  ],
                },
              },
              options: {
                update_selection: true,
              },
            }
          : {
              kind: "insert_content" as const,
              content: {
                format: "json" as const,
                value: {
                  type: "paragraph",
                  content: [
                    {
                      type: "text",
                      text: target.title || "无标题",
                      marks: [{ type: "link", attrs: { href } }],
                    },
                  ],
                },
              },
              options: {
                update_selection: true,
              },
            };
        void (async () => {
          await executeRuntimeCommand({ kind: "focus" });
          await executeRuntimeCommand(insertCommand);
          await executeRuntimeCommand({ kind: "select_textblock_end" });
        })().catch((error) => {
          emitFailure(
            "command_failed",
            error instanceof Error ? error.message : "插入块级引用失败",
          );
        });
        return { blockId };
      },
      undo: () => {
        void executeRuntimeCommand({ kind: "undo" }).catch((error) => {
          emitFailure(
            "command_failed",
            error instanceof Error ? error.message : "撤销失败",
          );
        });
      },
      redo: () => {
        void executeRuntimeCommand({ kind: "redo" }).catch((error) => {
          emitFailure(
            "command_failed",
            error instanceof Error ? error.message : "重做失败",
          );
        });
      },
      getCursorBlockId: () => resolveCurrentBlockId(),
      replaceWithSnapshot: (blocks: Json) => {
        if (!runtimeLoadedRef.current || generationRef.current == null) return;
        void syncExternalBlocksToRuntime(blocks, { emitUpdate: true }).catch((error) => {
          emitFailure(
            "command_failed",
            error instanceof Error ? error.message : "写入 inline editor 内容失败",
          );
        });
      },
      requestFallbackToBlockNote: () => {
        requestFallbackToBlockNote("通过页面壳显式切回 BlockNote");
      },
    };
    registerBridge(bridge);
    return () => registerBridge(null);
  }, [
    emitFailure,
    executeRuntimeCommand,
    registerBridge,
    requestFallbackToBlockNote,
    resolveCurrentBlockId,
    resolveCurrentBlockRange,
    syncExternalBlocksToRuntime,
  ]);

  const runToolbarCommand = useCallback(
    (command: unknown) => {
      void executeRuntimeCommand(command)
        .then(() => {
          setColorMenuOpen(false);
          window.setTimeout(() => {
            updateToolbarAnchor();
          }, 0);
        })
        .catch((error) => {
          emitFailure(
            "command_failed",
            error instanceof Error ? error.message : "执行工具栏命令失败",
          );
        });
    },
    [emitFailure, executeRuntimeCommand, updateToolbarAnchor],
  );

  return (
    <div
      ref={shellRef}
      className="relative rounded-xl border border-emerald-200/80 bg-white/90 shadow-sm"
      data-editor-host-kind="leptos_tiptap_runtime"
      data-runtime-editor-status={status}
      onMouseMove={(event) => {
        if (blockMenuBlock) {
          return;
        }
        updateHoveredBlockFromTarget(event.target);
      }}
      onMouseLeave={() => {
        if (!blockMenuBlock) {
          setHoveredBlock(null);
        }
      }}
    >
      <div className="flex items-center justify-between border-b border-emerald-100 px-3 py-2 text-xs text-emerald-700">
        <span>主编辑器：`leptos-tiptap` 正式 island</span>
        <span>{status}</span>
      </div>
      {errorMessage ? (
        <div className="border-b border-red-100 bg-red-50 px-3 py-2 text-xs text-red-700">
          {errorMessage}
        </div>
      ) : (
        <div className="border-b border-emerald-100/80 bg-emerald-50/60 px-3 py-2 text-xs text-emerald-800">
          现在默认接入真正的 Tiptap runtime，并补回 `slash`、浮动工具条、块级菜单和
          turn-into。
        </div>
      )}

      {toolbarAnchor ? (
        <div
          className="pointer-events-none absolute z-30"
          style={{
            top: `${toolbarAnchor.top}px`,
            left: `${toolbarAnchor.left}px`,
            transform: "translate(-50%, -100%)",
          }}
        >
          <div
            className="pointer-events-auto flex items-center gap-1 rounded-2xl border border-emerald-200 bg-white/95 p-2 shadow-xl"
            data-inline-floating-menu="true"
            data-testid="mnote-leptos-tiptap-inline-toolbar"
            onMouseDown={(event) => event.preventDefault()}
          >
            <button
              type="button"
              className={`rounded-lg px-2 py-1 text-xs ${
                selectionState.bold ? "bg-emerald-600 text-white" : "bg-slate-100 text-slate-700"
              }`}
              onClick={() => runToolbarCommand({ kind: "toggle_bold" })}
            >
              B
            </button>
            <button
              type="button"
              className={`rounded-lg px-2 py-1 text-xs ${
                selectionState.italic
                  ? "bg-emerald-600 text-white"
                  : "bg-slate-100 text-slate-700"
              }`}
              onClick={() => runToolbarCommand({ kind: "toggle_italic" })}
            >
              I
            </button>
            <button
              type="button"
              className={`rounded-lg px-2 py-1 text-xs ${
                selectionState.underline
                  ? "bg-emerald-600 text-white"
                  : "bg-slate-100 text-slate-700"
              }`}
              onClick={() => runToolbarCommand({ kind: "toggle_underline" })}
            >
              U
            </button>
            <button
              type="button"
              className={`rounded-lg px-2 py-1 text-xs ${
                selectionState.strike
                  ? "bg-emerald-600 text-white"
                  : "bg-slate-100 text-slate-700"
              }`}
              onClick={() => runToolbarCommand({ kind: "toggle_strike" })}
            >
              S
            </button>
            <button
              type="button"
              className={`rounded-lg px-2 py-1 text-xs ${
                selectionState.highlight
                  ? "bg-amber-400 text-slate-900"
                  : "bg-slate-100 text-slate-700"
              }`}
              onClick={() =>
                runToolbarCommand({
                  kind: "toggle_highlight",
                  attributes: { color: "#fef08a" },
                })
              }
            >
              标黄
            </button>
            <div className="relative">
              <button
                type="button"
                className={`rounded-lg px-2 py-1 text-xs ${
                  selectionState.text_style
                    ? "bg-emerald-600 text-white"
                    : "bg-slate-100 text-slate-700"
                }`}
                onClick={() => setColorMenuOpen((value) => !value)}
              >
                颜色
              </button>
              {colorMenuOpen ? (
                <div
                  className="absolute left-0 top-10 z-40 grid min-w-[150px] grid-cols-3 gap-2 rounded-xl border border-emerald-200 bg-white p-2 shadow-xl"
                  data-inline-floating-menu="true"
                  onMouseDown={(event) => event.preventDefault()}
                >
                  {[
                    "#111827",
                    "#1d4ed8",
                    "#18794e",
                    "#b91c1c",
                    "#7c3aed",
                    "#c96b1f",
                  ].map((color) => (
                    <button
                      key={color}
                      type="button"
                      className="h-8 rounded-full border border-slate-200"
                      style={{ backgroundColor: color }}
                      onClick={() =>
                        runToolbarCommand({
                          kind: "set_color",
                          attributes: { color },
                        })
                      }
                    />
                  ))}
                  <button
                    type="button"
                    className="col-span-3 rounded-lg bg-slate-100 px-2 py-1 text-xs text-slate-700"
                    onClick={() => runToolbarCommand({ kind: "unset_color" })}
                  >
                    清除颜色
                  </button>
                </div>
              ) : null}
            </div>
          </div>
        </div>
      ) : null}

      {slashMenu ? (
        <div
          className="absolute z-30 w-[320px] max-w-[calc(100%-32px)]"
          style={{
            top: `${slashMenu.anchor.top + 20}px`,
            left: `${slashMenu.anchor.left}px`,
            transform: "translateX(-20%)",
          }}
          data-inline-floating-menu="true"
          data-testid="mnote-leptos-tiptap-inline-slash-menu"
          onMouseDown={(event) => event.preventDefault()}
        >
          <div className="rounded-2xl border border-emerald-200 bg-white/95 p-3 shadow-2xl">
            <div className="mb-2">
              <div className="text-sm font-semibold text-slate-900">Slash 菜单</div>
              <div className="text-xs text-slate-500">
                来自真正的 Tiptap 命令链，而不是占位文本框。
              </div>
            </div>
            <div className="grid gap-1">
              {SLASH_ACTIONS.map((action, index) => {
                const active = index === slashIndex;
                return (
                  <button
                    key={action.id}
                    type="button"
                    className={`rounded-xl px-3 py-2 text-left ${
                      active
                        ? "bg-emerald-600 text-white"
                        : "bg-slate-50 text-slate-800 hover:bg-emerald-50"
                    }`}
                    onClick={() => {
                      void runSlashOrTurnIntoAction(slashMenu.blockIndex, action.id)
                        .then(() => {
                          setSlashMenu(null);
                        })
                        .catch((error) => {
                          emitFailure(
                            "command_failed",
                            error instanceof Error ? error.message : "执行 slash 命令失败",
                          );
                        });
                    }}
                  >
                    <div className="text-sm font-medium">{action.label}</div>
                    <div
                      className={`text-xs ${
                        active ? "text-emerald-50" : "text-slate-500"
                      }`}
                    >
                      {action.description}
                    </div>
                  </button>
                );
              })}
            </div>
          </div>
        </div>
      ) : null}

      {hoveredBlock ? (
        <div
          className="pointer-events-none absolute left-2 z-20 flex items-center gap-1"
          style={{
            top: `${hoveredBlock.top + Math.min(hoveredBlock.height / 2, 20)}px`,
            transform: "translateY(-50%)",
          }}
        >
          <button
            type="button"
            className="pointer-events-auto flex h-8 w-8 items-center justify-center rounded-full border border-emerald-200 bg-white text-sm text-emerald-700 shadow"
            data-inline-block-handle="true"
            onMouseDown={(event) => event.preventDefault()}
            onClick={() => {
              void insertParagraphAfter(hoveredBlock.index).catch((error) => {
                emitFailure(
                  "command_failed",
                  error instanceof Error ? error.message : "插入块失败",
                );
              });
            }}
          >
            +
          </button>
          <button
            type="button"
            className="pointer-events-auto flex h-8 items-center justify-center rounded-full border border-emerald-200 bg-white px-2 text-xs text-slate-700 shadow"
            data-inline-block-handle="true"
            data-testid="mnote-leptos-tiptap-inline-block-handle"
            onMouseDown={(event) => event.preventDefault()}
            onClick={() => {
              setSlashMenu(null);
              setToolbarAnchor(null);
              setColorMenuOpen(false);
              setBlockMenuBlock(hoveredBlock);
            }}
          >
            ⠿
          </button>
        </div>
      ) : null}

      {blockMenuBlock ? (
        <div
          className="absolute left-12 z-30 w-[280px]"
          style={{
            top: `${blockMenuBlock.top + 8}px`,
          }}
          data-inline-floating-menu="true"
          data-testid="mnote-leptos-tiptap-inline-block-menu"
          onMouseDown={(event) => event.preventDefault()}
        >
          <div className="rounded-2xl border border-emerald-200 bg-white/95 p-3 shadow-2xl">
            <div className="mb-3">
              <div className="text-sm font-semibold text-slate-900">
                当前块：{blockMenuBlock.label}
              </div>
              <div className="text-xs text-slate-500">
                这里是主编辑区内的块级命令面，不再只是 bare editor。
              </div>
            </div>
            <div className="grid gap-1">
              <button
                type="button"
                className="rounded-xl bg-slate-50 px-3 py-2 text-left text-sm text-slate-800 hover:bg-emerald-50"
                onClick={() => {
                  void insertParagraphAfter(blockMenuBlock.index)
                    .then(() => setBlockMenuBlock(null))
                    .catch((error) => {
                      emitFailure(
                        "command_failed",
                        error instanceof Error ? error.message : "插入块失败",
                      );
                    });
                }}
              >
                在下方插入空段落
              </button>
              <button
                type="button"
                className="rounded-xl bg-slate-50 px-3 py-2 text-left text-sm text-slate-800 hover:bg-emerald-50"
                onClick={() => {
                  void duplicateBlockAt(blockMenuBlock.index)
                    .then(() => setBlockMenuBlock(null))
                    .catch((error) => {
                      emitFailure(
                        "command_failed",
                        error instanceof Error ? error.message : "复制块失败",
                      );
                    });
                }}
              >
                复制当前块
              </button>
              <button
                type="button"
                className="rounded-xl bg-slate-50 px-3 py-2 text-left text-sm text-slate-800 hover:bg-emerald-50"
                onClick={() => {
                  void runSlashOrTurnIntoAction(blockMenuBlock.index, "heading1")
                    .then(() => setBlockMenuBlock(null))
                    .catch((error) => {
                      emitFailure(
                        "command_failed",
                        error instanceof Error ? error.message : "转换标题失败",
                      );
                    });
                }}
              >
                Turn into 一级标题
              </button>
              <button
                type="button"
                className="rounded-xl bg-slate-50 px-3 py-2 text-left text-sm text-slate-800 hover:bg-emerald-50"
                onClick={() => {
                  void runSlashOrTurnIntoAction(blockMenuBlock.index, "todo")
                    .then(() => setBlockMenuBlock(null))
                    .catch((error) => {
                      emitFailure(
                        "command_failed",
                        error instanceof Error ? error.message : "转换待办失败",
                      );
                    });
                }}
              >
                Turn into 待办列表
              </button>
              <button
                type="button"
                className="rounded-xl bg-slate-50 px-3 py-2 text-left text-sm text-slate-800 hover:bg-emerald-50"
                onClick={() => {
                  void runSlashOrTurnIntoAction(blockMenuBlock.index, "quote")
                    .then(() => setBlockMenuBlock(null))
                    .catch((error) => {
                      emitFailure(
                        "command_failed",
                        error instanceof Error ? error.message : "转换引用失败",
                      );
                    });
                }}
              >
                Turn into 引用块
              </button>
              <button
                type="button"
                className="rounded-xl bg-slate-50 px-3 py-2 text-left text-sm text-slate-800 hover:bg-emerald-50"
                onClick={() => {
                  void runSlashOrTurnIntoAction(blockMenuBlock.index, "code_block")
                    .then(() => setBlockMenuBlock(null))
                    .catch((error) => {
                      emitFailure(
                        "command_failed",
                        error instanceof Error ? error.message : "转换代码块失败",
                      );
                    });
                }}
              >
                Turn into 代码块
              </button>
              <button
                type="button"
                className="rounded-xl bg-red-50 px-3 py-2 text-left text-sm text-red-700 hover:bg-red-100"
                onClick={() => {
                  void deleteBlockAt(blockMenuBlock.index)
                    .then(() => setBlockMenuBlock(null))
                    .catch((error) => {
                      emitFailure(
                        "command_failed",
                        error instanceof Error ? error.message : "删除块失败",
                      );
                    });
                }}
              >
                删除当前块
              </button>
            </div>
          </div>
        </div>
      ) : null}

      <div
        id={mountId}
        className="min-h-[720px] px-8 py-6"
        data-testid="mnote-leptos-tiptap-runtime-editor-root"
      />

      <div className="border-t border-emerald-100 px-3 py-2 text-[11px] text-slate-500">
        当前块：{currentBlockLabel}，Slash 菜单、浮动工具条、块级菜单都已接到 `leptos-tiptap` runtime。
      </div>
    </div>
  );
}

export const LeptosTiptapInlineEditorHost = LeptosTiptapRuntimeEditorHost;
