"use client";

import "simple-mind-map/dist/simpleMindMap.esm.css";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { createReactBlockSpec } from "@blocknote/react";
import type { Block, BlockNoteEditor } from "@blocknote/core";
import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
import type { CustomBlockSchema } from "../schema";
import { MindmapToolbar } from "./MindmapToolbar";
import { MindmapSidebar } from "./MindmapSidebar";
import { MindmapSidebarTrigger } from "./MindmapSidebarTrigger";
import { MindmapNavigator } from "./MindmapNavigator";
import { MindmapMiniMap } from "./MindmapMiniMap";
import { MindmapCount } from "./MindmapCount";
import type { SidebarPanel } from "./mindmapSidebarConfig";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { ArrowUp, ArrowDown, ArrowLeft, ArrowRight } from "lucide-react";
import iconConfig from "./mindmapIconConfig";
import { emitAssetsChanged } from "@/lib/events";

// 避免 SSR/Node 环境提前评估 simple-mind-map 内部依赖 document
// @ts-expect-error 第三方库缺少类型定义
const loadIconModules = async () => {
  const { nodeIconList } = await import("simple-mind-map/src/svg/icons.js");
  const { mergerIconList } = await import("simple-mind-map/src/utils/index.js");
  return { nodeIconList, mergerIconList };
};

// 安全获取图片尺寸
const getImageSizeSafe = (url: string): Promise<{ width: number; height: number } | null> =>
  new Promise((resolve) => {
    if (!url) return resolve(null);
    const img = new Image();
    img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
    img.onerror = () => resolve(null);
    img.src = url;
  });

type MindMapInstance = {
  execCommand: (...args: unknown[]) => void;
  destroy: () => void;
  setData: (data: unknown) => void;
  getData: (withConfig?: boolean) => unknown;
  on?: (event: string, handler: (...args: unknown[]) => void) => void;
  emit?: (event: string, ...args: unknown[]) => void;
  off?: (event: string, handler: (...args: unknown[]) => void) => void;
  setMode?: (mode: string) => void;
  command: { clearHistory: () => void };
        view: { fit: () => void; scale: number; enlarge: () => void; narrow: () => void; setScale: (scale: number, cx: number, cy: number) => void };
  renderer: { activeNodeList: unknown[]; renderTree: { _node: unknown }; setRootNodeCenter: () => void };
  painter?: { startPainter: () => void };
  doExport?: { export: (type: string, isDownload?: boolean, name?: string) => Promise<unknown> };
  width: number;
  height: number;
  miniMap?: unknown;
  getThemeConfig: (key: string) => unknown;
  setThemeConfig: (config: unknown) => void;
  setTheme: (theme: string) => void;
  setLayout: (layout: string) => void;
};

type MindMapNode = {
  getStyle?: (key: string, inherit?: boolean) => unknown;
  getData?: (key: string) => unknown;
  nodeData?: { data?: Record<string, unknown> };
  data?: Record<string, unknown>;
};

type MindMapData = {
  data: Record<string, unknown>;
  children?: unknown[];
};

export const defaultMindmapData = {
  data: { text: "中心主题" },
  children: [],
};

const STORAGE_PREFIX = "wolai-mindmap-autosave-";

function downloadJson(data: unknown, name: string) {
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
  const url = URL.createObjectURL(blob);
  const anchor = document.createElement("a");
  anchor.href = url;
  anchor.download = `${name}.json`;
  anchor.click();
  URL.revokeObjectURL(url);
}

const createSimplePrompt = (title: string, placeholder = "") => {
  const value = window.prompt(title, placeholder);
  if (!value || !value.trim()) return null;
  return value.trim();
};

// 修补 svg.js rbox 在节点未挂载时抛出的异常
const patchSvgRbox = async () => {
  // 仅在浏览器环境生效
  if (typeof window === "undefined") return;
  const svgModule = await import("@svgdotjs/svg.js");
  const candidates = [(svgModule as any).Element, (svgModule as any).G];
  candidates.forEach((Ctor) => {
    if (!Ctor?.prototype) return;
    if (Ctor.prototype.__wolaiRboxPatched) return;
    const original = Ctor.prototype.rbox;
    if (typeof original !== "function") return;
    // @ts-expect-error 动态扩展第三方原型
    Ctor.prototype.rbox = function patchedRbox(ref?: unknown) {
      try {
        return original.call(this, ref);
      } catch (error) {
        console.warn("rbox 失败，返回空边界框以避免崩溃", error);
        return {
          x: 0,
          y: 0,
          width: 0,
          height: 0,
          x2: 0,
          y2: 0,
          cx: 0,
          cy: 0,
        };
      }
    };
    Ctor.prototype.__wolaiRboxPatched = true;
  });
};

const applyToActiveNodes = (
  mindmap: MindMapInstance | null,
  handler: (node: unknown) => void,
) => {
  if (!mindmap) {
    window.alert("思维导图尚未初始化");
    return;
  }
  const list = mindmap.renderer?.activeNodeList;
  if (!list || list.length === 0) {
    // 尝试兜底选中根节点
    const root = mindmap.renderer?.root ?? mindmap.renderer?.renderTree?._node;
    if (root) {
      mindmap.execCommand?.("SET_NODE_ACTIVE", root, true);
    } else {
      window.alert("请选择至少一个节点再执行该操作");
      return;
    }
  }
  (mindmap.renderer?.activeNodeList ?? []).forEach(handler);
};

// 确保存在激活节点后再执行命令，若无则自动选中根节点
const ensureActiveBefore = (mindmap?: MindMapInstance | null) => {
  const mm = mindmap ?? null;
  if (!mm) {
    window.alert("思维导图尚未初始化");
    return null;
  }
  const list = mm.renderer?.activeNodeList ?? [];
  if (!list || list.length === 0) {
    const root = mm.renderer?.root ?? mm.renderer?.renderTree?._node;
    if (root) {
      mm.execCommand?.("SET_NODE_ACTIVE", root, true);
    } else {
      window.alert("请先选中一个节点");
      return null;
    }
  }
  return mm;
};

const MindmapBlockView = ({
  block,
  editor,
  fullscreen = false,
}: {
  block: Block<CustomBlockSchema, "mindmap">;
  editor: BlockNoteEditor<CustomBlockSchema>;
  fullscreen?: boolean;
}) => {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const fileInputRef = useRef<HTMLInputElement | null>(null);
  const [mindmap, setMindmap] = useState<MindMapInstance | null>(null);
  const [canBack, setCanBack] = useState(false);
  const [canForward, setCanForward] = useState(false);
  const [activeNodes, setActiveNodes] = useState<unknown[]>([]);
  const [painterMode, setPainterMode] = useState(false);
  const [showMiniMap, setShowMiniMap] = useState(false);
  const [activeSidebar, setActiveSidebar] = useState<SidebarPanel | null>(null);
  const [showNoteModal, setShowNoteModal] = useState(false);
  const [noteContent, setNoteContent] = useState("");

  const getRootNode = useCallback((mm?: MindMapInstance | null) => {
    const instance = mm ?? mindmap;
    return instance?.renderer?.root ?? instance?.renderer?.renderTree?._node ?? null;
  }, [mindmap]);

  const docId = useMemo(
    () =>
      block.props.docId ||
      (typeof window !== "undefined"
        ? window.location.pathname.split("/").pop() ?? ""
        : ""),
    [block.props.docId],
  );

  const autosaveKey = useMemo(
    () => `${STORAGE_PREFIX}${docId || block.id}`,
    [block.id, docId],
  );
  const initialDataRef = useRef<unknown>(null);
  if (initialDataRef.current === null) {
    const cached = typeof window !== "undefined" ? window.localStorage.getItem(autosaveKey) : null;
    if (cached) {
      try {
        initialDataRef.current = JSON.parse(cached);
      } catch {
        initialDataRef.current = block.props.data ?? defaultMindmapData;        
      }
    } else {
      initialDataRef.current = block.props.data ?? defaultMindmapData;
    }
  }

  // 优先加载本地文件，其次 Supabase（通过后端 API）
  useEffect(() => {
    let cancelled = false;
    if (!docId) return;
    (async () => {
      try {
        const resp = await fetch(`/api/mindmap/${docId}`);
        if (!resp.ok) return;
        const payload = await resp.json().catch(() => null);
        const data = payload?.data;
        if (!data || cancelled) return;
        initialDataRef.current = data;
        if (mindmap) {
          mindmap.setData(data);
          mindmap.command.clearHistory();
        }
      } catch (error) {
        console.warn("加载本地/远端思维导图失败", error);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [docId, mindmap]);

  const persistData = useCallback(
    (data: unknown) => {
      if (!editor) return;
      window.localStorage.setItem(autosaveKey, JSON.stringify(data));     
      editor.updateBlock(block, { props: { ...block.props, data } });     
      if (docId) {
        // 同步到本地文件 + Supabase（弱依赖）
        fetch(`/api/mindmap/${docId}`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ data }),
        })
          .then((resp) => {
            if (resp.ok) {
              emitAssetsChanged(docId, { id: `mindmap-${docId}`, document_id: docId, asset_type: "mindmap" });
            }
          })
          .catch((err) => console.warn("思维导图同步失败", err));
      }
    },
    [autosaveKey, block, docId, editor],
  );

  const debouncedPersist = useDebouncedCallback((data: unknown) => {
    persistData(data);
  }, 500);

  const initialSyncDone = useRef(false);
  useEffect(() => {
    if (!docId || !mindmap || initialSyncDone.current) return;
    initialSyncDone.current = true;
    const data = mindmap.getData?.() ?? initialDataRef.current ?? defaultMindmapData;
    void fetch(`/api/mindmap/${docId}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ data }),
    })
      .then((resp) => {
        if (resp.ok) {
          emitAssetsChanged(docId, { id: `mindmap-${docId}`, document_id: docId, asset_type: "mindmap" });
        }
      })
      .catch((err) => console.warn("初次创建思维导图文件失败", err));
  }, [docId, mindmap, initialDataRef]);

  // 初始选中根节点，后续不强制抢焦点，允许用户自由选择
  useEffect(() => {
    if (!mindmap || activeNodes.length > 0) return;
    const rootNode = mindmap.renderer?.root ?? mindmap.renderer?.renderTree?._node;
    if (rootNode) {
      setActiveNodes([rootNode]);
      mindmap.renderer.activeNodeList = [rootNode];
      // @ts-expect-error 第三方库字段
      mindmap.renderer.lastActiveNodeList = [rootNode];
      mindmap.emit?.("node_active", rootNode, [rootNode]);
    }
  }, [mindmap, activeNodes.length]);

  useEffect(() => {
    let destroyed = false;
    (async () => {
      if (!containerRef.current) return;
      const [
        { default: MindMap },
        { default: Painter },
        { default: AssociativeLine },
        { default: OuterFrame },
        { default: Exporter },
        { default: Formula },
        { default: RichText },
        { default: MiniMapPlugin },
        { default: Select },
        { default: Drag },
        { default: KeyboardNavigation },
        { default: NodeImgAdjust },
        { default: Scrollbar },
        { default: RainbowLines },
        { default: Watermark },
        { default: TouchEvent },
        { default: Cooperate },
        { default: Demonstrate },
        { default: MindMapLayoutPro },
        { default: NodeBase64ImageStorage },
        { default: ExportPDF },
        { default: ExportXMind },
      ] = await Promise.all([
        import("simple-mind-map"),
        import("simple-mind-map/src/plugins/Painter.js"),
        import("simple-mind-map/src/plugins/AssociativeLine.js"),
        import("simple-mind-map/src/plugins/OuterFrame.js"),
        import("simple-mind-map/src/plugins/Export.js"),
        import("simple-mind-map/src/plugins/Formula.js"),
        import("simple-mind-map/src/plugins/RichText.js"),
        import("simple-mind-map/src/plugins/MiniMap.js"),
        import("simple-mind-map/src/plugins/Select.js"),
        import("simple-mind-map/src/plugins/Drag.js"),
        import("simple-mind-map/src/plugins/KeyboardNavigation.js"),
        import("simple-mind-map/src/plugins/NodeImgAdjust.js"),
        import("simple-mind-map/src/plugins/Scrollbar.js"),
        import("simple-mind-map/src/plugins/RainbowLines.js"),
        import("simple-mind-map/src/plugins/Watermark.js"),
        import("simple-mind-map/src/plugins/TouchEvent.js"),
        import("simple-mind-map/src/plugins/Cooperate.js"),
        import("simple-mind-map/src/plugins/Demonstrate.js"),
        import("simple-mind-map/src/plugins/MindMapLayoutPro.js"),
        import("simple-mind-map/src/plugins/NodeBase64ImageStorage.js"),
        import("simple-mind-map/src/plugins/ExportPDF.js"),
        import("simple-mind-map/src/plugins/ExportXMind.js"),
      ]);

      const plugins = [
        { name: "Painter", plugin: Painter },
        { name: "AssociativeLine", plugin: AssociativeLine },
        { name: "OuterFrame", plugin: OuterFrame },
        { name: "Export", plugin: Exporter },
        { name: "Formula", plugin: Formula },
        { name: "RichText", plugin: RichText },
        { name: "MiniMap", plugin: MiniMapPlugin },
        { name: "Select", plugin: Select },
        { name: "Drag", plugin: Drag },
        { name: "KeyboardNavigation", plugin: KeyboardNavigation },
        { name: "NodeImgAdjust", plugin: NodeImgAdjust },
        { name: "Scrollbar", plugin: Scrollbar },
        { name: "RainbowLines", plugin: RainbowLines },
        { name: "Watermark", plugin: Watermark },
        { name: "TouchEvent", plugin: TouchEvent },
        { name: "Cooperate", plugin: Cooperate },
        { name: "Demonstrate", plugin: Demonstrate },
        { name: "MindMapLayoutPro", plugin: MindMapLayoutPro },
        { name: "NodeBase64ImageStorage", plugin: NodeBase64ImageStorage },
        { name: "ExportPDF", plugin: ExportPDF },
        { name: "ExportXMind", plugin: ExportXMind },
      ];

      plugins.forEach(({ name, plugin }) => {
        if (!plugin) {
          console.warn(`思维导图插件加载失败：${name}`);
          return;
        }
        // @ts-expect-error 第三方库缺少类型定义
        if (MindMap.hasPlugin(plugin) === -1) {
          console.log(`注册插件: ${name}`);
          const registerPlugin =
            // @ts-expect-error simple-mind-map 插件注册无类型声明
            (MindMap as { usePlugin?: (p: unknown) => void }).usePlugin;
          if (registerPlugin) {
            registerPlugin(plugin);
          }
        }
      });

      const { nodeIconList, mergerIconList } = await loadIconModules();

      await patchSvgRbox();

      const hostEl = containerRef.current ?? document.body;

      const instance = new MindMap({
        el: hostEl,
        data: initialDataRef.current,
        theme: "classic",
        layout: "logicalStructure",
        mousewheelAction: "zoom",
        enableFreeDrag: true,
        enableCtrlKeyNodeSelection: true,
        fit: true,
        useLeftKeySelectionRightKeyDrag: true,
        // 传入扩展图标表，和官方一致
        iconList: mergerIconList([
          ...nodeIconList,
          ...(iconConfig as unknown[]),
        ]),
      }) as MindMapInstance;
      // 确保测量节点的缓存容器始终挂在有效 DOM 上，避免 appendChild 空指针
      if (!(instance as any).commonCaches) {
        (instance as any).commonCaches = {};
      }
      if (!(instance as any).commonCaches.measureRichtextNodeTextSizeEl) {
        const measureDiv = document.createElement("div");
        measureDiv.style.position = "fixed";
        measureDiv.style.left = "-999999px";
        ((instance as any).commonCaches).measureRichtextNodeTextSizeEl = measureDiv;
        (hostEl ?? document.body).appendChild(measureDiv);
      }
      instance.setMode?.("edit");

      if (typeof window !== "undefined") {
        // 便于开发阶段在控制台直接调试实例
        // @ts-expect-error 调试用全局变量
        window.__mindmapInstance = instance;
      }

      setMindmap(instance);

      instance.on?.("back_forward", (index: number, len: number) => {
        setCanBack(index > 0);
        setCanForward(index < len - 1);
      });
      instance.on?.("node_active", (_node: unknown, list: unknown[]) => {      
        if (!list || list.length === 0) return;
        setActiveNodes(list || []);
      });
      instance.on?.("node_click", (node: unknown) => {
        // 确保点击即可激活（调用节点自身的 active 逻辑，保持和官方一致）
        // @ts-expect-error 第三方库节点对象
        if (typeof node?.active === "function") {
          // @ts-expect-error simple-mind-map 节点对象缺少类型
          node.active();
        } else {
          // 兜底：手动维护激活列表
          // @ts-expect-error simple-mind-map 渲染器缺少类型
          instance.renderer?.clearActiveNodeList?.();
          // @ts-expect-error simple-mind-map 渲染器缺少类型
          instance.renderer?.addNodeToActiveList?.(node, true);
          // @ts-expect-error simple-mind-map 渲染器缺少类型
          instance.renderer?.emitNodeActiveEvent?.(node);
        }
        const list = instance.renderer?.activeNodeList ?? [];
        setActiveNodes(list.length === 0 && node ? [node] : (list as unknown[]));
      });
      instance.on?.("painter_start", () => setPainterMode(true));
      instance.on?.("painter_end", () => setPainterMode(false));
      instance.on?.("data_change", (data: unknown) => {
        debouncedPersist(data);
      });
      const rootNode = getRootNode(instance);
      if (rootNode) {
        setActiveNodes([rootNode]);
      }

      if (destroyed) {
        instance.destroy();
      }
    })();

    return () => {
      destroyed = true;
      setMindmap((prev) => {
        prev?.destroy();
        return null;
      });
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [block.id]);

  const handleUndo = () => mindmap?.execCommand("BACK");
  const handleRedo = () => mindmap?.execCommand("FORWARD");
  const handlePainter = () => {
    if (!mindmap?.painter) {
      window.alert("格式刷插件未就绪");
      return;
    }
    mindmap.painter.startPainter();
  };
  const handleSibling = () => {
    if (!mindmap) return;
    window.setTimeout(() => mindmap.execCommand("INSERT_NODE"), 0);
  };
  const handleChild = () => {
    if (!mindmap) return;
    window.setTimeout(() => mindmap.execCommand("INSERT_CHILD_NODE"), 0);
  };
  const handleDelete = () => {
    if (!mindmap) return;
    window.setTimeout(() => mindmap.execCommand("REMOVE_NODE"), 0);
  };
  const handleSummary = () => {
    const mm = ensureActiveBefore(mindmap);
    if (mm) {
      window.setTimeout(() => mm.execCommand("ADD_GENERALIZATION"), 0);
    }
  };
  const handleAssociativeLine = () => {
    const mm = ensureActiveBefore(mindmap);
    if (mm) {
      window.setTimeout(() => mm.execCommand("ADD_ASSOCIATIVE_LINE"), 0);
    }
  };
  const handleOuterFrame = () => {
    const mm = ensureActiveBefore(mindmap);
    if (mm) {
      window.setTimeout(() => mm.execCommand("ADD_OUTER_FRAME"), 0);
    }
  };

  const [showImageModal, setShowImageModal] = useState(false);
  const [imageUrl, setImageUrl] = useState("");
  const [imageWidth, setImageWidth] = useState<number | string>(260);
  const [imageHeight, setImageHeight] = useState<number | string>(200);
  const [imageTitle, setImageTitle] = useState("");
  const [imagePosition, setImagePosition] = useState<string>("top");
  const fileInputForImage = useRef<HTMLInputElement | null>(null);

  // 图片预览（双击节点图片）
  const [showImageViewer, setShowImageViewer] = useState(false);
  const [viewerSrc, setViewerSrc] = useState("");
  const [viewerMeta, setViewerMeta] = useState<{ title?: string; width?: number; height?: number }>({});
  const imgToolbarRef = useRef<HTMLDivElement | null>(null);
  const [imgToolbarState, setImgToolbarState] = useState({
    show: false,
    x: 0,
    y: 0,
    placement: "top" as "top" | "bottom" | "left" | "right",
  });
  const imgToolbarHover = useRef(false);

  const handleImage = () => {
    const first = mindmap?.renderer?.activeNodeList?.[0] as MindMapNode | undefined;
    if (first?.getStyle) {
      const placement = first.getStyle("imgPlacement", false) as string;        
      if (placement) setImagePosition(placement);
    }
    setShowImageModal(true);
  };

  // 预览：监听 mindmap 事件
  useEffect(() => {
    if (!mindmap) return;
    const handler = (node: MindMapNode, e?: Event) => {
      e?.stopPropagation?.();
      e?.preventDefault?.();
      const src =
        node?.nodeData?.data?.image ||
        node?.getData?.("image") ||
        node?.data?.image;
      if (src) {
        setViewerSrc(src);
        const size = node?.getData?.("imageSize") || {};
        const title = node?.getData?.("imageTitle") || "";
        setViewerMeta({
          title: typeof title === "string" ? title : "",
          width: Number(size?.width) || undefined,
          height: Number(size?.height) || undefined,
        });
        setShowImageViewer(true);
      }
    };
    const onActive = () => {
      const list = (mindmap.renderer?.activeNodeList || []) as MindMapNode[];
      const has = list.some((n) => !!n?.getData?.("image"));
      if (!has) setImgToolbarState((s) => ({ ...s, show: false }));
    };
    const showToolbarOnClick = (node: MindMapNode, _svgImg: unknown, evt: Event | undefined) => {
      const target = evt?.target as Element | undefined;
      const bbox = target?.getBoundingClientRect?.();
      const placement = (node?.getStyle?.("imgPlacement") || "top") as "top" | "bottom" | "left" | "right";
      if (!bbox) return;
      setImagePosition(placement);
      setImgToolbarState({
        show: true,
        x: bbox.left,
        y: bbox.top,
        placement,
      });
    };
    const hideToolbar = () => {
      if (!imgToolbarHover.current) {
        setImgToolbarState((s) => ({ ...s, show: false }));
      }
    };
    mindmap.on?.("node_img_dblclick", handler);
    mindmap.on?.("node_active", onActive);
    mindmap.on?.("node_img_click", showToolbarOnClick);
    mindmap.on?.("draw_click", hideToolbar);
    return () => {
      mindmap.off?.("node_img_dblclick", handler);
      mindmap.off?.("node_active", onActive);
      mindmap.off?.("node_img_click", showToolbarOnClick);
      mindmap.off?.("draw_click", hideToolbar);
    };
  }, [mindmap]);

  // 悬浮图片位置工具条
  const renderImgToolbar = () => {
    if (!imgToolbarState.show) return null;
    const { x, y, placement } = imgToolbarState;
    const setPlacement = (p: "top" | "bottom" | "left" | "right") => {
      setImagePosition(p);
      applyToActiveNodes(mindmap, (node) =>
        mindmap?.execCommand?.("SET_NODE_STYLES", node, { imgPlacement: p }),   
      );
      setImgToolbarState((s) => ({ ...s, placement: p }));
    };
    const btn = (
      p: typeof placement,
      Icon: React.ComponentType<{ className?: string }>,
      title: string,
    ) => (
      <button
        key={p}
        type="button"
        className={`flex h-8 w-8 items-center justify-center rounded border bg-white/90 text-gray-700 shadow ${
          placement === p ? "border-blue-500 text-blue-600" : "border-gray-200"
        }`}
        onMouseDown={(e) => {
          e.stopPropagation();
          e.preventDefault();
          setPlacement(p);
        }}
        title={title}
      >
        <Icon className="h-4 w-4" />
      </button>
    );
    return (
      <div
        ref={imgToolbarRef}
        className="pointer-events-auto fixed z-[9999] flex gap-1 rounded-lg bg-black/50 p-2 backdrop-blur"
        style={{ left: x, top: y - 42 }}
        onMouseEnter={() => {
          imgToolbarHover.current = true;
          setImgToolbarState((s) => ({ ...s, show: true }));
        }}
        onMouseLeave={() => {
          imgToolbarHover.current = false;
          setImgToolbarState((s) => ({ ...s, show: false }));
        }}
      >
        {btn("top", ArrowUp, "顶部")}
        {btn("bottom", ArrowDown, "底部")}
        {btn("left", ArrowLeft, "靠左")}
        {btn("right", ArrowRight, "靠右")}
      </div>
    );
  };

  const handleIcon = () => {
    setActiveSidebar("icons");
  };

  const handleLink = () => {
    const href = createSimplePrompt("请输入超链接");
    if (!href) return;
    applyToActiveNodes(mindmap, (node) => mindmap?.execCommand("SET_NODE_HYPERLINK", node, href, href));
  };

  const handleNote = () => {
    setShowNoteModal(true);
  };

  const handleTag = () => {
    const tagsRaw = createSimplePrompt("请输入标签，使用逗号分隔", "重点,待验证");
    if (!tagsRaw) return;
    const tags = tagsRaw.split(",").map((item) => item.trim()).filter(Boolean);
    applyToActiveNodes(mindmap, (node) => mindmap?.execCommand("SET_NODE_TAG", node, tags));
  };

  const handleFormula = () => {
    setActiveSidebar("formula");
  };

  const handleNoteConfirm = () => {
    const note = noteContent.trim();
    setShowNoteModal(false);
    if (!note) return;
    applyToActiveNodes(mindmap, (node) => mindmap?.execCommand("SET_NODE_NOTE", node, note));
  };

  const handleImageConfirm = () => {
    const url = imageUrl.trim();
    const width = Number(imageWidth) || 260;
    const height = Number(imageHeight) || 200;
    if (!url) {
      window.alert("请输入图片链接");
      return;
    }
    setShowImageModal(false);
    applyToActiveNodes(mindmap, (node) =>
      mindmap?.execCommand("SET_NODE_IMAGE", node, {
        url,
        width,
        height,
        title: imageTitle || "",
        position: imagePosition || "top",
      }),
    );
    applyToActiveNodes(mindmap, (node) =>
      mindmap?.execCommand?.("SET_NODE_STYLES", node, {
        imgPlacement: imagePosition || "top",
      }),
    );
  };

  const handleAttachment = () => {
    const url = createSimplePrompt("附件链接（http/https）");
    if (!url) return;
    const name = createSimplePrompt("附件名称（可选）", "附件");
    applyToActiveNodes(mindmap, (node) => mindmap?.execCommand("SET_NODE_ATTACHMENT", node, url, name ?? ""));
  };

  const handleAiPlaceholder = () => {
    window.alert("AI 能力占位：后续接入大模型生成/优化节点内容。");
  };

  const handleImport = async (event: React.ChangeEvent<HTMLInputElement>) => {  
    const file = event.target.files?.[0];
    if (!file) return;
    const reset = () => {
      event.target.value = "";
    };
    const ext = (file.name.split(".").pop() || "").toLowerCase();
    try {
      // JSON / smm
      if (ext === "json" || ext === "smm") {
        const text = await file.text();
        const data = JSON.parse(text);
        mindmap?.setData(data);
        mindmap?.command.clearHistory();
        debouncedPersist(data);
        return;
      }

      // XMind
      if (ext === "xmind") {
        const xmindParser = await import("simple-mind-map/src/parse/xmind.js");
        const blob = new Blob([await file.arrayBuffer()]);
        const data = await xmindParser.default.parseXmindFile(blob, (content: unknown[]) => {
          const list = Array.isArray(content) ? content : [];
          if (list.length > 1) {
            window.alert("检测到 XMind 多画布，自动导入第一个画布。");
          }
          return list.length > 0 ? list[0] : content;
        });
        mindmap?.setData(data);
        mindmap?.command.clearHistory();
        debouncedPersist(data);
        return;
      }

      // Markdown
      if (ext === "md" || ext === "markdown") {
        const { transformMarkdownTo } = await import(
          "simple-mind-map/src/parse/markdownTo.js"
        );
        const text = await file.text();
        const data = transformMarkdownTo(text) as MindMapData;
        if (!data.data) {
          data.data = { text: file.name.replace(/\.(md|markdown)$/i, "") || "中心主题" };
        }
        mindmap?.setData(data);
        mindmap?.command.clearHistory();
        debouncedPersist(data);
        return;
      }

      window.alert("暂不支持该文件类型，请选择 .json / .smm / .xmind / .md");
    } catch (error) {
      console.error(error);
      window.alert("导入失败：文件格式或内容错误");
    } finally {
      reset();
    }
  };

  const handleNew = () => {
    if (!window.confirm("确定要新建空白导图吗？当前未保存的修改将被覆盖。")) return;
    mindmap?.setData(defaultMindmapData);
    mindmap?.command.clearHistory();
    debouncedPersist(defaultMindmapData);
  };

  const handleOpenDirectory = () => {
    const saved = window.localStorage.getItem(autosaveKey);
    if (!saved) {
      window.alert("暂无本地目录记录，可使用“导入”载入文件。");
      return;
    }
    try {
      const data = JSON.parse(saved);
      mindmap?.setData(data);
      window.alert("已从本地目录恢复最新自动保存版本。");
    } catch {
      window.alert("本地目录数据损坏，建议重新导入。");
    }
  };

  const handleExportJson = () => {
    const data = mindmap?.getData?.(true) ?? block.props.data ?? defaultMindmapData;
    downloadJson(data, "mindmap");
  };

  const handleExport = async (type: string, name = "mindmap") => {
    try {
      await mindmap?.doExport?.export(type, true, name);
    } catch (error) {
      console.error(error);
      window.alert(`导出 ${type.toUpperCase()} 失败，请稍后再试`);
    }
  };

  const handleExportPng = () => handleExport("png");
  const handleExportSvg = () => handleExport("svg");
  const handleExportPdf = () => handleExport("pdf");
  const handleExportMd = () => handleExport("md");
  const handleExportTxt = () => handleExport("txt");
  const handleExportXmind = () => handleExport("xmind");

  const handleSaveAs = () => handleExportJson();

  const handleDeleteMindmap = useCallback(async () => {
    if (!docId) {
      editor.removeBlocks([block.id]);
      return;
    }
    const confirmed = window.confirm("确认删除当前思维导图？此操作会移除文件并清空侧边栏记录。");
    if (!confirmed) return;
    const resp = await fetch(`/api/mindmap/${docId}`, { method: "DELETE" });
    if (!resp.ok) {
      const payload = await resp.json().catch(() => ({}));
      window.alert(payload?.error ?? "删除思维导图失败");
      return;
    }
    try {
      window.localStorage.removeItem(autosaveKey);
    } catch {
      // ignore
    }
    emitAssetsChanged(docId);
    editor.removeBlocks([block.id]);
  }, [autosaveKey, block.id, docId, editor]);

  const toolbarProps = {
    canBack,
    canForward,
    painterMode,
    onUndo: handleUndo,
    onRedo: handleRedo,
    onPainter: handlePainter,
    onSibling: handleSibling,
    onChild: handleChild,
    onDelete: handleDelete,
    onImage: handleImage,
    onIcon: handleIcon,
    onLink: handleLink,
    onNote: handleNote,
    onTag: handleTag,
    onSummary: handleSummary,
    onAssociativeLine: handleAssociativeLine,
    onFormula: handleFormula,
    onAttachment: handleAttachment,
    onOuterFrame: handleOuterFrame,
    onAi: handleAiPlaceholder,
    onImport: handleImport,
    onNew: handleNew,
    onOpenDirectory: handleOpenDirectory,
    onSaveAs: handleSaveAs,
    onDeleteMindmap: handleDeleteMindmap,
    onExportJson: handleExportJson,
    onExportPng: handleExportPng,
    onExportSvg: handleExportSvg,
    onExportPdf: handleExportPdf,
    onExportMd: handleExportMd,
    onExportTxt: handleExportTxt,
    onExportXmind: handleExportXmind,
    fileInputRef,
  };

  const noteModal = showNoteModal ? (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
      <div className="w-full max-w-4xl rounded-xl bg-white shadow-2xl">
        <div className="flex items-center justify-between border-b px-4 py-3">  
          <span className="text-lg font-semibold text-gray-800">备注</span>     
          <button className="text-gray-400 hover:text-gray-600" onClick={() => setShowNoteModal(false)}>
            ✕
          </button>
        </div>
        <div className="px-4 py-3">
          <textarea
            className="h-64 w-full resize-none rounded-md border border-gray-200 p-3 text-sm focus:border-blue-400 focus:outline-none"
            placeholder="支持富文本/Markdown，内容将写入节点备注"
            value={noteContent}
            onChange={(e) => setNoteContent(e.target.value)}
          />
        </div>
        <div className="flex items-center justify-end gap-3 border-t px-4 py-3">
          <button
            className="rounded-md border border-gray-200 px-4 py-2 text-sm text-gray-600 hover:bg-gray-50"
            onClick={() => setShowNoteModal(false)}
          >
            取消
          </button>
          <button
            className="rounded-md bg-blue-500 px-4 py-2 text-sm text-white hover:bg-blue-600"
            onClick={handleNoteConfirm}
          >
            确定
          </button>
        </div>
      </div>
    </div>
  ) : null;

  const imageViewer = showImageViewer ? (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4" onClick={() => setShowImageViewer(false)}>
      <div className="relative max-h-full max-w-5xl text-white">
        <button
          className="absolute -top-3 -right-3 rounded-full bg-white/90 px-2 py-1 text-sm text-gray-700 shadow"
          onClick={(e) => {
            e.stopPropagation();
            setShowImageViewer(false);
          }}
        >
          关闭
        </button>
        <div className="mb-2 flex items-center justify-between gap-4 px-1 text-xs text-gray-200">
          <span className="truncate">{viewerMeta.title || "图片预览"}</span>    
          <span className="shrink-0">{viewerMeta.width && viewerMeta.height ? `${viewerMeta.width} × ${viewerMeta.height}px` : ""}</span>
        </div>
        {/* eslint-disable-next-line @next/next/no-img-element */}
        <img
          src={viewerSrc}
          alt={viewerMeta.title || "预览"}
          className="max-h-[80vh] max-w-[80vw] rounded-lg shadow-2xl object-contain bg-white"
          onClick={(e) => e.stopPropagation()}
        />
      </div>
    </div>
  ) : null;

  const imgToolbar = renderImgToolbar();

  const imageModal = showImageModal ? (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
      <div className="w-full max-w-3xl rounded-xl bg-white shadow-2xl">
        <div className="flex items-center justify-between border-b px-4 py-3">  
          <span className="text-lg font-semibold text-gray-800">插入图片</span> 
          <button className="text-gray-400 hover:text-gray-600" onClick={() => setShowImageModal(false)}>
            ✕
          </button>
        </div>
          <div className="space-y-4 px-4 py-3">
          <div className="space-y-2">
            <Label className="text-xs font-semibold text-gray-600">方式一：上传图片</Label>
            <div className="flex items-center gap-2">
              <button
                className="rounded-md border border-gray-200 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50"
                onClick={() => fileInputForImage.current?.click()}
              >
                选择文件
              </button>
              <span className="text-xs text-gray-400 truncate">
                {imageUrl.startsWith("data:") ? "已选择本地图片" : "未选择文件"}
              </span>
              <input
                ref={fileInputForImage}
                type="file"
                accept="image/*"
                className="hidden"
                onChange={async (e) => {
                  const file = e.target.files?.[0];
                  if (!file) return;
                  const reader = new FileReader();
                  reader.onload = async () => {
                    const dataUrl = String(reader.result);
                    setImageUrl(dataUrl);
                    const size = await getImageSizeSafe(dataUrl);
                    if (size) {
                      setImageWidth(size.width);
                      setImageHeight(size.height);
                    }
                  };
                  reader.readAsDataURL(file);
                }}
              />
            </div>
          </div>
          <div className="space-y-2">
            <Label className="text-xs font-semibold text-gray-600">方式二：图片地址</Label>
            <Input
              value={imageUrl}
              onChange={(e) => setImageUrl(e.target.value)}
              placeholder="https://example.com/image.png"
            />
          </div>
          <div className="space-y-2">
            <Label className="text-xs font-semibold text-gray-600">快捷位置</Label>
            <div className="flex gap-2">
              <button
                type="button"
                className={`flex-1 rounded border px-2 py-2 text-sm ${imagePosition === "top" ? "border-blue-500 bg-blue-50 text-blue-600" : "border-gray-200"}`}
                onClick={() => setImagePosition("top")}
                title="顶部"
              >
                <ArrowUp className="mx-auto h-4 w-4" />
              </button>
              <button
                type="button"
                className={`flex-1 rounded border px-2 py-2 text-sm ${imagePosition === "bottom" ? "border-blue-500 bg-blue-50 text-blue-600" : "border-gray-200"}`}
                onClick={() => setImagePosition("bottom")}
                title="底部"
              >
                <ArrowDown className="mx-auto h-4 w-4" />
              </button>
              <button
                type="button"
                className={`flex-1 rounded border px-2 py-2 text-sm ${imagePosition === "left" ? "border-blue-500 bg-blue-50 text-blue-600" : "border-gray-200"}`}
                onClick={() => setImagePosition("left")}
                title="靠左"
              >
                <ArrowLeft className="mx-auto h-4 w-4" />
              </button>
              <button
                type="button"
                className={`flex-1 rounded border px-2 py-2 text-sm ${imagePosition === "right" ? "border-blue-500 bg-blue-50 text-blue-600" : "border-gray-200"}`}
                onClick={() => setImagePosition("right")}
                title="靠右"
              >
                <ArrowRight className="mx-auto h-4 w-4" />
              </button>
            </div>
          </div>
          <div className="grid grid-cols-2 gap-3">
            <div className="space-y-1">
              <Label className="text-xs text-gray-500">宽度（px）</Label>
              <Input
                type="number"
                value={imageWidth}
                onChange={(e) => setImageWidth(e.target.value)}
                min={10}
                placeholder="260"
              />
            </div>
            <div className="space-y-1">
              <Label className="text-xs text-gray-500">高度（px）</Label>
              <Input
                type="number"
                value={imageHeight}
                onChange={(e) => setImageHeight(e.target.value)}
                min={10}
                placeholder="200"
              />
            </div>
          </div>
          <div className="space-y-1">
            <Label className="text-xs text-gray-500">标题（可选）</Label>
            <Input
              value={imageTitle}
              onChange={(e) => setImageTitle(e.target.value)}
              placeholder="图片标题"
            />
          </div>
          <div className="space-y-1">
            <Label className="text-xs text-gray-500">位置</Label>
            <select
              className="h-9 w-full rounded-md border border-gray-200 px-3 text-sm"
              value={imagePosition}
              onChange={(e) => setImagePosition(e.target.value)}
            >
              <option value="center">居中</option>
              <option value="top">顶部</option>
              <option value="bottom">底部</option>
              <option value="left">靠左</option>
              <option value="right">靠右</option>
            </select>
          </div>
          <p className="text-xs text-gray-400">可选择本地图片或粘贴 URL，默认尺寸 260×200。</p>
        </div>
        <div className="flex items-center justify-end gap-3 border-t px-4 py-3">  
          <button
            className="rounded-md border border-gray-200 px-4 py-2 text-sm text-gray-600 hover:bg-gray-50"
            onClick={() => setShowImageModal(false)}
          >
            取消
          </button>
          <button
            className="rounded-md bg-blue-500 px-4 py-2 text-sm text-white hover:bg-blue-600"
            onClick={handleImageConfirm}
          >
            插入
          </button>
        </div>
      </div>
    </div>
  ) : null;

  if (fullscreen) {
    return (
      <div className="relative h-screen w-screen overflow-hidden bg-white">     
        <div className="fixed left-1/2 top-4 z-20 flex w-fit -translate-x-1/2 transform items-center justify-center gap-4">
          <MindmapToolbar {...toolbarProps} />
        </div>
        <div className="relative h-full w-full">
          <div
            ref={containerRef}
            className="h-full w-full"
            contentEditable={false}
          />
          
          <MindmapSidebarTrigger 
            activeSidebar={activeSidebar} 
            onSelect={setActiveSidebar} 
          />
          {/* @ts-expect-error mindmap type */}
          <MindmapSidebar 
            mindmap={mindmap} 
            activeNodes={activeNodes} // @ts-expect-error type
            activeTab={activeSidebar}
            onClose={() => setActiveSidebar(null)}
          />
          <MindmapNavigator
            mindmap={mindmap}
            fullscreen={fullscreen}
            onToggleMiniMap={() => setShowMiniMap(!showMiniMap)}
            miniMapOpen={showMiniMap}
          />
          <MindmapCount mindmap={mindmap} />
          {/* @ts-expect-error mindmap type */}
          <MindmapMiniMap mindmap={mindmap} show={showMiniMap} />
          {imgToolbar}
          {noteModal}
          {imageModal}
          {imageViewer}
        </div>
      </div>
    );
  }

  return (
    <div className="not-prose my-4 w-full overflow-hidden rounded-xl border border-gray-200 bg-[#f8fafc] shadow-sm">
      <div className="flex flex-col gap-3 border-b border-gray-100 bg-white px-4 py-3">
        <div className="w-full">
          <MindmapToolbar {...toolbarProps} />
        </div>
      </div>
      <div className="relative h-[520px] w-full overflow-hidden bg-white">
        <div
          ref={containerRef}
          className="h-full w-full"
          contentEditable={false}
        />
        
        <MindmapSidebarTrigger 
            activeSidebar={activeSidebar} 
            onSelect={setActiveSidebar} 
        />
        {/* @ts-expect-error mindmap type */}
        <MindmapSidebar 
            mindmap={mindmap} 
            activeNodes={activeNodes} // @ts-expect-error type
            activeTab={activeSidebar}
            onClose={() => setActiveSidebar(null)}
        />
        <MindmapNavigator
            mindmap={mindmap}
            fullscreen={fullscreen}
            onToggleMiniMap={() => setShowMiniMap(!showMiniMap)}
            miniMapOpen={showMiniMap}
        />
        <MindmapCount mindmap={mindmap} />
        {/* @ts-expect-error mindmap type */}
        <MindmapMiniMap mindmap={mindmap} show={showMiniMap} />
        {imgToolbar}
        {noteModal}
        {imageModal}
        {imageViewer}
      </div>
    </div>
  );
};

export { MindmapBlockView };

export const mindmapBlock = createReactBlockSpec(
  {
    type: "mindmap",
    propSchema: {
      docId: { default: "" },
      data: { default: defaultMindmapData },
    },
    content: "none",
  },
  {
    render: (props) => <MindmapBlockView {...props} />,
  },
);
