"use client";

import {
  useEffect,
  useRef,
  useState,
  useMemo,
  useCallback,
  type JSX,
  type MouseEvent as ReactMouseEvent,
} from "react";
import { createReactBlockSpec } from "@blocknote/react";
import type { Block, BlockNoteEditor } from "@blocknote/core";
import { Download, Image as ImageIcon, Link as LinkIcon, MoreHorizontal, Paperclip, RefreshCcw, Trash, Type } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useImagePicker } from "@/components/media/image-picker-context";
import type { MediaKind } from "@/types/media";
import { emitAssetsChanged } from "@/lib/events";
import { getMnoteRuntimeConfig } from "@/lib/runtime-config";
import { useCurrentDocumentStore } from "@/store/current-document";
import { toBrowserAccessibleUrl } from "@/lib/url/browser-file-url";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import type { CustomBlockSchema } from "../schema";

type MediaAlign = "left" | "center" | "right";

type MediaBlockRenderProps = {
  block: Block<CustomBlockSchema> & { props: any };
  editor: BlockNoteEditor<CustomBlockSchema>;
};

const TYPE_LABEL_MAP: Record<MediaKind, string> = {
  image: "图片",
  video: "视频",
  audio: "音频",
  file: "文件",
};

const deriveFileName = (value?: string) => {
  if (!value) {
    return "未命名资源";
  }
  try {
    const url = new URL(value);
    const last = url.pathname.split("/").filter(Boolean).pop();
    if (last) {
      return decodeURIComponent(last);
    }
  } catch {
    const segments = value.split("?")[0]?.split("/") ?? [];
    const last = segments.pop();
    if (last) {
      return decodeURIComponent(last);
    }
  }
  return "未命名资源";
};

const formatFileSize = (size?: number | null) => {
  if (!size || size <= 0) {
    return "未知大小";
  }
  const units = ["B", "KB", "MB", "GB", "TB"];
  let idx = 0;
  let current = size;
  while (current >= 1024 && idx < units.length - 1) {
    current /= 1024;
    idx += 1;
  }
  return `${current.toFixed(current >= 10 ? 0 : 1)} ${units[idx]}`;
};

const MediaBlockContent = ({ block, editor }: any) => {
  const { openPicker } = useImagePicker();
  const [busy, setBusy] = useState(false);
  const fileUrl = block.props.fileUrl as string;
  const browserFileUrl = useMemo(() => toBrowserAccessibleUrl(fileUrl) ?? fileUrl, [fileUrl]);
  const rawThumbUrl = (block.props.thumbnailUrl as string | undefined) || "";
  const browserThumbUrl = useMemo(
    () => (rawThumbUrl ? toBrowserAccessibleUrl(rawThumbUrl) ?? rawThumbUrl : ""),
    [rawThumbUrl],
  );
  const rawAssetType = (block.props.assetType as string) || "image";
  const assetType: MediaKind =
    rawAssetType === "video" || rawAssetType === "audio" || rawAssetType === "file"
      ? (rawAssetType as MediaKind)
      : "image";
  const typeLabel = TYPE_LABEL_MAP[assetType] ?? TYPE_LABEL_MAP.image;
  const canAlign = assetType === "image" || assetType === "video";
  const canToggleBorder = assetType === "image";
  const canTriggerOcr = assetType === "image";
  const canResize = assetType === "image" || assetType === "video";
  const [dragging, setDragging] = useState<null | { side: "left" | "right"; startX: number; startWidth: number }>(null);
  const [localWidth, setLocalWidth] = useState(() => (block.props.width ? Number(block.props.width) : 0));
  const mediaRef = useRef<HTMLDivElement | null>(null);
  const latestWidthRef = useRef(localWidth);
  const displayFileName = block.props.fileName ?? deriveFileName(fileUrl);
  const captionRef = useRef<HTMLInputElement | null>(null);
  const [captionEditing, setCaptionEditing] = useState(false);
  const shouldShowCaption = captionEditing || Boolean(block.props.caption);     
  const officeBase = getMnoteRuntimeConfig().onlyofficeBaseUrl;
  const resolveDocumentId = useCallback(() => {
    if (typeof window !== "undefined") {
      const [, tail] = window.location.pathname.split("/documents/");
      if (tail) {
        const id = tail.split(/[/?#]/)[0];
        if (id) return id;
      }
    }
    return (block.props as { documentId?: string })?.documentId || "";
  }, [block.props]);
  const extension = useMemo(() => {
    const name = (block.props.fileName || deriveFileName(fileUrl)).toLowerCase();
    const match = /\.([a-z0-9]+)$/.exec(name);
    return match?.[1] ?? "";
  }, [block.props.fileName, fileUrl]);
  const isOfficeDoc = useMemo(
    () =>
      ["doc", "docx", "rtf", "ppt", "pptx", "xls", "xlsx", "odp", "ods", "odt", "pdf", "csv"].includes(
        extension,
      ),
    [extension],
  );

  const handleChoose = () => {
    openPicker({
      defaultTab: fileUrl ? "recent" : "upload",
      mediaType: assetType,
      onSelect: (selection) => {
        editor.updateBlock(block, {
          props: {
            fileUrl: selection.fileUrl,
            thumbnailUrl: selection.thumbnailUrl ?? selection.fileUrl,
            assetId: selection.assetId,
            assetType: selection.assetType ?? rawAssetType,
            fileName: selection.fileName ?? block.props.fileName ?? "",
            fileSize: selection.fileSize ?? block.props.fileSize ?? null,       
            mimeType: selection.mimeType ?? block.props.mimeType ?? "",
            ocrStatus: "idle",
            documentId: resolveDocumentId(),
          },
        });
      },
    });
  };

  const toggleBorder = () => {
    editor.updateBlock(block, { props: { hasBorder: !block.props.hasBorder } });
  };

  const setAlign = (align: MediaAlign) => {
    editor.updateBlock(block, { props: { captionAlign: align } });
  };

  const handleCaptionChange = (value: string) => {
    editor.updateBlock(block, { props: { caption: value } });
  };

  const enableCaptionEdit = () => {
    setCaptionEditing(true);
    setTimeout(() => captionRef.current?.focus(), 0);
  };

  useEffect(() => {
    if (!shouldShowCaption && captionEditing) {
      setCaptionEditing(false);
    }
  }, [captionEditing, shouldShowCaption]);
  useEffect(() => {
    if (!dragging) {
      setLocalWidth(block.props.width ? Number(block.props.width) : 0);
    }
  }, [block.props.width, dragging]);
  useEffect(() => {
    latestWidthRef.current = localWidth;
  }, [localWidth]);

  const resolvedWidth = useMemo(() => {
    if (!canResize) return 0;
    if (localWidth > 0) return clampWidth(localWidth);
    if (block.props.width && Number(block.props.width) > 0) {
      return clampWidth(Number(block.props.width));
    }
    return 0;
  }, [block.props.width, canResize, localWidth]);

  const handleResizeStart = (event: ReactMouseEvent<HTMLSpanElement>, side: "left" | "right") => {
    if (!canResize) return;
    event.preventDefault();
    event.stopPropagation();
    const canvasWidth = resolvedWidth || mediaRef.current?.offsetWidth || 0;
    if (!canvasWidth) {
      return;
    }
    setDragging({
      side,
      startX: event.clientX,
      startWidth: canvasWidth,
    });
  };

  useEffect(() => {
    if (!dragging) {
      return undefined;
    }
    const handleMove = (event: MouseEvent) => {
      event.preventDefault();
      const delta = event.clientX - dragging.startX;
      const adjusted = dragging.side === "left" ? -delta : delta;
      const next = clampWidth(dragging.startWidth + adjusted);
      setLocalWidth(next);
    };
    const handleUp = () => {
      const finalWidth = latestWidthRef.current > 0 ? latestWidthRef.current : dragging.startWidth;
      editor.updateBlock(block, { props: { width: clampWidth(finalWidth) } });
      setDragging(null);
    };
    window.addEventListener("mousemove", handleMove);
    window.addEventListener("mouseup", handleUp);
    return () => {
      window.removeEventListener("mousemove", handleMove);
      window.removeEventListener("mouseup", handleUp);
    };
  }, [dragging, editor, block]);

  const handleLink = () => {
    const next = window.prompt(`输入${typeLabel}跳转链接`, block.props.linkUrl ?? "");
    if (next === null) return;
    editor.updateBlock(block, { props: { linkUrl: next.trim() } });
  };

  const resolveAssetId = async () => {
    const direct = (block.props as { assetId?: string })?.assetId;
    if (direct) return direct;

    // 说明：历史数据/迁移场景下，media block 可能丢失 assetId，导致：
    // - PDF 打开拿到的不是原文件（旧链接过期/返回 HTML）
    // - OnlyOffice callback 缺少 assetId，进而“不能保存”
    // 这里尝试通过 documentId + fileName 在 media_assets 中反查 assetId。
    const docId = (block.props as { documentId?: string })?.documentId || resolveDocumentId();
    const name = String(block.props.fileName || block.props.caption || "").trim();
    if (!docId || !name) return "";

    try {
      const res = await fetch(
        `/api/media/by-document?documentId=${encodeURIComponent(docId)}&limit=500`,
      );
      if (!res.ok) return "";
      const payload = (await res.json().catch(() => null)) as { items?: Array<{ id?: string; file_name?: string | null }> } | null;
      const items = Array.isArray(payload?.items) ? payload!.items! : [];
      const hit = items.find((it) => String(it.file_name || "") === name);
      return hit?.id ? String(hit.id) : "";
    } catch {
      return "";
    }
  };

  const resolveLatestFileUrl = async () => {
    if (!fileUrl) return "";
    const assetId = await resolveAssetId();
    if (!assetId) return fileUrl;
    try {
      const res = await fetch(`/api/media/sign?assetId=${encodeURIComponent(assetId)}`);
      if (!res.ok) return fileUrl;
      const payload = (await res.json().catch(() => null)) as { signedUrl?: string } | null;
      return payload?.signedUrl || fileUrl;
    } catch {
      return fileUrl;
    }
  };

  const viewOriginal = async () => {
    const url = await resolveLatestFileUrl();
    if (!url) return;
    window.open(url, "_blank", "noopener,noreferrer");
  };

  const openWithOnlyOffice = async () => {
    if (!fileUrl) return;
    if (!officeBase) {
      window.alert("缺少 ONLYOFFICE 地址，请在 .env.all 配置 NEXT_PUBLIC_ONLYOFFICE_BASE_URL");
      return;
    }
    const currentDocReadOnly = useCurrentDocumentStore.getState().readOnly;
    try {
      const assetId = await resolveAssetId();
      const res = assetId
        ? await fetch(`/api/media/sign?assetId=${encodeURIComponent(assetId)}`)
        : await fetch(
            `/api/media/signed-url?fileUrl=${encodeURIComponent(fileUrl)}&fileName=${encodeURIComponent(
              displayFileName,
            )}&for=onlyoffice`,
          );
      if (!res.ok) {
        const payload = await res.json().catch(() => null);
        throw new Error(payload?.error ?? "生成签名链接失败");
      }
      const { signedUrl } = (await res.json()) as { signedUrl: string };
      const target = new URL("/onlyoffice", window.location.origin);
      target.searchParams.set("fileUrl", signedUrl);
      target.searchParams.set("fileName", displayFileName);
      target.searchParams.set("fileType", extension || "docx");
      const docId = resolveDocumentId();
      if (docId) {
        target.searchParams.set("documentId", docId);
      }
      if (assetId) {
        target.searchParams.set("assetId", assetId);
      }
      target.searchParams.set("mode", currentDocReadOnly ? "view" : "edit");
      window.open(target.toString(), "_blank", "noopener,noreferrer");
    } catch (error) {
      window.alert((error as Error).message);
    }
  };

  const currentDocumentId = useCurrentDocumentStore((state) => state.documentId);
  const currentDisableDownload = useCurrentDocumentStore((state) => state.disableDownload);
  const resolvedDocIdForRestriction = resolveDocumentId();
  const downloadDisabled =
    Boolean(currentDisableDownload) &&
    Boolean(resolvedDocIdForRestriction) &&
    String(currentDocumentId ?? "") === String(resolvedDocIdForRestriction);

  const downloadAsset = async () => {
    if (downloadDisabled) {
      window.alert("该页面已禁止下载");
      return;
    }
    const url = await resolveLatestFileUrl();
    if (!url) return;
    const anchor = document.createElement("a");
    anchor.href = url;
    anchor.download = block.props.fileName || block.props.caption || typeLabel; 
    anchor.click();
  };

  const handleDeleteAsset = async () => {
    const assetId = (block.props as { assetId?: string })?.assetId;
    if (!assetId) {
      editor.removeBlocks([block.id]);
      return;
    }
    const docId = resolveDocumentId();
    const resp = await fetch("/api/media/batch", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ action: "delete", assetIds: [assetId] }),
    });
    if (!resp.ok) {
      const payload = await resp.json().catch(() => ({}));
      window.alert(payload?.error ?? "删除附件失败");
      return;
    }
    editor.removeBlocks([block.id]);
    emitAssetsChanged(docId);
  };

  const triggerOcr = async () => {
    if (!block.props.assetId) {
      window.alert("请先上传图片后再执行 OCR");
      return;
    }
    setBusy(true);
    try {
      const response = await fetch("/api/media/ocr", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ assetId: block.props.assetId }),
      });
      if (!response.ok) {
        const payload = await response.json().catch(() => null);
        throw new Error(payload?.error ?? "触发 OCR 失败");
      }
      editor.updateBlock(block, { props: { ocrStatus: "processing" } });
      window.alert("已提交 OCR 任务，稍后可在搜索面板中通过 OCR 结果搜索。");
    } catch (error) {
      window.alert((error as Error).message);
    } finally {
      setBusy(false);
    }
  };

  if (!fileUrl) {
    return (
      <div className="wolai-media wolai-media--empty">
        <Button type="button" variant="outline" onClick={handleChoose} className="gap-2">
          <ImageIcon className="h-4 w-4" />
          选择或上传{typeLabel}
        </Button>
        <p className="text-xs text-gray-500">支持上传、最近及外链插入</p>
      </div>
    );
  }

  const renderPreviewContent = () => {
    if (assetType === "video") {
      return (
        <video
          controls
          className="max-h-[420px] w-full rounded-2xl bg-black"
          poster={browserThumbUrl || undefined}
          style={resolvedWidth ? { width: `${resolvedWidth}px` } : undefined}
        >
          <source src={browserFileUrl} type={block.props.mimeType || "video/mp4"} />
        </video>
      );
    }
    if (assetType === "audio") {
      return (
        <div className="rounded-2xl border border-gray-200 bg-white/80 p-5">
          <audio controls className="w-full">
            <source src={browserFileUrl} type={block.props.mimeType || "audio/mpeg"} />
          </audio>
          <p className="mt-2 text-sm text-gray-500">{displayFileName}</p>
        </div>
      );
    }
    if (assetType === "file") {
      // 根据文件扩展名确定图标颜色
      const getIconColor = () => {
        const ext = (extension ?? "").toLowerCase().replace(/^\./, "");
        if (ext === "pdf") return "text-red-500";
        if (["doc", "docx"].includes(ext)) return "text-blue-600";
        if (["xls", "xlsx"].includes(ext)) return "text-green-600";
        if (["ppt", "pptx"].includes(ext)) return "text-orange-500";
        return "text-[#9B9A97]";
      };

        return (
          <div
            role="button"
            tabIndex={0}
            onClick={() => {
              if (isOfficeDoc) {
                void openWithOnlyOffice();
              } else {
                void downloadAsset();
              }
            }}
            onKeyDown={(e) => {
              if (e.key === "Enter" || e.key === " ") {
                e.preventDefault();
                if (isOfficeDoc) {
                  void openWithOnlyOffice();
                } else {
                  void downloadAsset();
                }
              }
            }}
            data-testid="wolai-media-file-row"
            className="group flex h-[26px] items-center gap-2 rounded-[4px] border border-transparent bg-transparent px-2 outline-none transition-colors duration-200 cursor-pointer select-none hover:border-[#E9E9E8] hover:bg-[#F7F7F5] focus:outline-none focus-visible:outline-none"
          >
            <span className={cn("w-5 h-5 flex-none flex items-center justify-center", getIconColor())}>
              <Paperclip className="h-4 w-4" />
            </span>
            <div className="flex min-w-0 flex-1 flex-row items-center gap-2 overflow-hidden">
              <span className="min-w-0 flex-1 truncate text-[14px] text-[#37352F] font-normal">
                {displayFileName}
              </span>
              {block.props.fileSize ? (
                <span className="shrink-0 text-[12px] text-[#999999]">{formatFileSize(block.props.fileSize)}</span>
              ) : null}
            </div>
            <div className="ml-auto flex shrink-0 items-center gap-1">
              <button
                type="button"
                data-testid="wolai-media-file-download"
                className="inline-flex h-6 w-6 items-center justify-center rounded-[4px] text-[#9B9A97] hover:bg-[#EDEDED] hover:text-[#37352F]"
                aria-label={downloadDisabled ? "已禁止下载" : "下载"}
                title={downloadDisabled ? "已禁止下载" : "下载"}
                disabled={downloadDisabled}
                onMouseDown={(e) => {
                  e.preventDefault();
                  e.stopPropagation();
                }}
                onClick={(e) => {
                  e.preventDefault();
                  e.stopPropagation();
                  void downloadAsset();
                }}
              >
                <Download className="h-4 w-4" />
              </button>
              <DropdownMenu>
                <DropdownMenuTrigger asChild>
                  <button
                    type="button"
                    data-testid="wolai-media-file-more"
                    className="inline-flex h-6 w-6 items-center justify-center rounded-[4px] text-[#9B9A97] hover:bg-[#EDEDED] hover:text-[#37352F]"
                    aria-label="更多操作"
                    title="更多操作"
                    onMouseDown={(e) => {
                      e.preventDefault();
                      e.stopPropagation();
                    }}
                    onClick={(e) => {
                      e.preventDefault();
                      e.stopPropagation();
                    }}
                  >
                    <MoreHorizontal className="h-4 w-4" />
                  </button>
                </DropdownMenuTrigger>
                <DropdownMenuContent align="end" className="w-56">
                  <DropdownMenuItem onClick={handleChoose}>替换资源</DropdownMenuItem>
                  {!shouldShowCaption && <DropdownMenuItem onClick={enableCaptionEdit}>添加说明</DropdownMenuItem>}
                  <DropdownMenuItem onClick={handleLink}>{block.props.linkUrl ? "编辑链接" : "添加链接"}</DropdownMenuItem>
                  <DropdownMenuItem onClick={() => handleCopyLink(fileUrl)}>复制链接</DropdownMenuItem>
                  <DropdownMenuItem
                    onClick={() => {
                      void viewOriginal();
                    }}
                  >
                    查看原文件
                  </DropdownMenuItem>
                  {isOfficeDoc && <DropdownMenuItem onClick={() => void openWithOnlyOffice()}>使用 ONLYOFFICE 打开</DropdownMenuItem>}
                  <DropdownMenuItem
                    disabled={downloadDisabled}
                    onClick={() => {
                      void downloadAsset();
                    }}
                  >
                    {downloadDisabled ? "已禁止下载" : "下载到本地"}
                  </DropdownMenuItem>
                  <DropdownMenuSeparator />
                  <DropdownMenuItem onClick={handleDeleteAsset}>删除</DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>
            </div>
          </div>
        );
      }
      const inlineStyle = resolvedWidth ? { width: `${resolvedWidth}px` } : undefined;
      return (
        <img
          src={browserThumbUrl || browserFileUrl}
          alt={block.props.caption || typeLabel}
          style={inlineStyle}
        />
      );
    };

  const figure = (
    <figure
      className={cn(
        "wolai-media__figure",
        canToggleBorder && block.props.hasBorder && "wolai-media__figure--border",
        canAlign && block.props.captionAlign === "center" && "wolai-media__figure--center",
        canAlign && block.props.captionAlign === "right" && "wolai-media__figure--right",
      )}
    >
      <div className="wolai-media__preview">{renderPreviewContent()}</div>
      {shouldShowCaption && (
        <figcaption>
          <input
            ref={captionRef}
            value={block.props.caption ?? ""}
            onChange={(event) => handleCaptionChange(event.target.value)}
            onBlur={() => setCaptionEditing(false)}
            placeholder={assetType === "file" ? "添加文件说明..." : "添加说明..."}
            className="w-full border-none bg-transparent text-sm text-[#475569] outline-none"
          />
        </figcaption>
      )}
    </figure>
  );

  type QuickAction = { key: string; label: string; icon: JSX.Element; onClick: () => void };
  const quickActions: QuickAction[] = [
    {
      key: "replace",
      label: `替换${typeLabel}`,
      icon: <RefreshCcw className="h-4 w-4" />,
      onClick: handleChoose,
    },
    canToggleBorder
      ? {
          key: "border",
          label: block.props.hasBorder ? "取消边框" : "显示边框",
          icon: <ImageIcon className="h-4 w-4" />,
          onClick: toggleBorder,
        }
      : null,
    !shouldShowCaption
      ? {
          key: "caption",
          label: "添加说明",
          icon: <Type className="h-4 w-4" />,
          onClick: enableCaptionEdit,
        }
      : null,
    {
      key: "link",
      label: block.props.linkUrl ? "编辑链接" : "添加链接",
      icon: <LinkIcon className="h-4 w-4" />,
      onClick: handleLink,
    },
    !downloadDisabled
      ? {
          key: "download",
          label: `下载${typeLabel}`,
          icon: <Download className="h-4 w-4" />,
          onClick: () => {
            void downloadAsset();
          },
        }
      : null,
    {
      key: "delete",
      label: `删除${typeLabel}`,
      icon: <Trash className="h-4 w-4" />,
      onClick: handleDeleteAsset,
    },
  ].filter((action): action is QuickAction => Boolean(action));

  const dropdownLinkLabel = block.props.linkUrl ? "编辑链接" : "添加链接";

  return (
    <div className={cn("wolai-media", assetType === "file" && "wolai-media--file")} ref={mediaRef}>
      <div
        className="wolai-media__canvas"
        style={resolvedWidth ? { width: `${resolvedWidth}px` } : undefined}
          onDoubleClick={() => {
            if (assetType === "file" && isOfficeDoc) {
              void openWithOnlyOffice();
            } else if (assetType === "file") {
              void viewOriginal();
            }
          }}
      >
        {block.props.linkUrl ? (
          <a href={block.props.linkUrl} target="_blank" rel="noopener noreferrer">
            {figure}
          </a>
        ) : (
          figure
        )}
        {assetType !== "file" && (
        <div className="wolai-media__quickbar">
          {quickActions.map((action) => (
            <button
              key={action.key}
              type="button"
              className="wolai-media__quickbutton"
              onClick={action.onClick}
              title={action.label}
              aria-label={action.label}
            >
              {action.icon}
            </button>
          ))}
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <button type="button" className="wolai-media__quickbutton" aria-label="更多操作">
                <MoreHorizontal className="h-4 w-4" />
              </button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end" className="w-56">
              <DropdownMenuItem onClick={handleChoose}>替换资源</DropdownMenuItem>
              {!shouldShowCaption && (
                <DropdownMenuItem onClick={enableCaptionEdit}>添加说明</DropdownMenuItem>
              )}
              {canToggleBorder && (
                <DropdownMenuItem onClick={toggleBorder}>
                  {block.props.hasBorder ? "取消边框" : "显示边框"}
                </DropdownMenuItem>
              )}
              {canAlign && (
                <>
                  <DropdownMenuLabel className="text-xs text-gray-400">说明对齐</DropdownMenuLabel>
                  <DropdownMenuItem onClick={() => setAlign("left")}>左对齐</DropdownMenuItem>
                  <DropdownMenuItem onClick={() => setAlign("center")}>居中</DropdownMenuItem>
                  <DropdownMenuItem onClick={() => setAlign("right")}>右对齐</DropdownMenuItem>
                  <DropdownMenuSeparator />
                </>
              )}
               <DropdownMenuItem onClick={handleLink}>{dropdownLinkLabel}</DropdownMenuItem>
               <DropdownMenuItem onClick={() => handleCopyLink(fileUrl)}>复制链接</DropdownMenuItem>
               <DropdownMenuItem
                 onClick={() => {
                   void viewOriginal();
                 }}
               >
                 查看原文件
               </DropdownMenuItem>
               <DropdownMenuItem
                 disabled={downloadDisabled}
                 onClick={() => {
                   void downloadAsset();
                 }}
               >
                 {downloadDisabled ? "已禁止下载" : "下载到本地"}
               </DropdownMenuItem>
              {canTriggerOcr && (
                <>
                  <DropdownMenuSeparator />
                  <DropdownMenuItem disabled={busy} onClick={triggerOcr}>
                    {busy ? "OCR 进行中..." : "触发 OCR 识别"}
                  </DropdownMenuItem>
                </>
              )}
            </DropdownMenuContent>
          </DropdownMenu>
        </div>
        )}
        {canResize && (
          <>
            <ResizeHandle side="left" dragging={Boolean(dragging)} onMouseDown={(event) => handleResizeStart(event, "left")} />
            <ResizeHandle side="right" dragging={Boolean(dragging)} onMouseDown={(event) => handleResizeStart(event, "right")} />
          </>
        )}
      </div>
      <div className="wolai-media__hint">{block.props.ocrStatus === "processing" ? "OCR 处理中..." : ""}</div>
    </div>
  );
};

export const mediaBlock = createReactBlockSpec(
  {
    type: "media",
    propSchema: {
      fileUrl: { default: "", type: "string" },
      thumbnailUrl: { default: "", type: "string" },
      caption: { default: "", type: "string" },
      captionAlign: { default: "left", values: ["left", "center", "right"] as MediaAlign[] },
      hasBorder: { default: true, type: "boolean" },
      linkUrl: { default: "", type: "string" },
      assetId: { default: "", type: "string" },
      assetType: { default: "image", type: "string" },
      fileName: { default: "", type: "string" },
      fileSize: { default: 0, type: "number" },
      mimeType: { default: "", type: "string" },
      width: { default: 0, type: "number" },
      ocrStatus: { default: "idle", type: "string" },
      documentId: { default: "", type: "string" },
    },
    content: "none",
  },
  {
    render: (props) => <MediaBlockContent {...props} />,
  },
)();
const handleCopyLink = async (targetUrl: string | null) => {
  if (!targetUrl) return;
  try {
    if (navigator?.clipboard?.writeText) {
      await navigator.clipboard.writeText(targetUrl);
      window.alert("链接已复制");
    } else {
      throw new Error("no clipboard");
    }
  } catch {
    window.prompt("请复制以下链接", targetUrl);
  }
};

const ResizeHandle = ({
  side,
  onMouseDown,
  dragging,
}: {
  side: "left" | "right";
  dragging: boolean;
  onMouseDown: (event: ReactMouseEvent<HTMLSpanElement>) => void;
}) => (
  <span
    role="separator"
    tabIndex={0}
    aria-orientation="horizontal"
    onMouseDown={onMouseDown}
    className={cn("wolai-media__resize-handle", `wolai-media__resize-handle--${side}`, dragging && "is-dragging")}
  />
);

const clampWidth = (value: number) => {
  const min = 240;
  const max = 960;
  if (Number.isNaN(value)) return min;
  return Math.max(min, Math.min(max, value));
};
