import {
  canonicalizeMindmapData,
  createDefaultMindmapThemeConfig,
  DEFAULT_MINDMAP_LAYOUT,
  DEFAULT_MINDMAP_THEME,
  defaultMindmapData,
  type MindMapData,
} from "./mindmap-projection";

export type MindmapAdapterProjection = {
  schema: "mnote.mindmap.simple_mind_map_scene.v1";
  runtime: "simple-mind-map" | string;
  root: unknown;
  layout?: unknown;
  theme?: unknown;
  themeConfig?: unknown;
  view?: unknown;
  config?: unknown;
  compatPayload?: unknown;
  kernelRevision: number;
};

export type SimpleMindMapInstance = {
  execCommand?: (command: string, ...args: unknown[]) => unknown;
  destroy?: () => void;
  getData?: (withConfig?: boolean) => unknown;
  getLayout?: () => unknown;
  getTheme?: () => unknown;
  getThemeConfig?: (prop?: unknown) => unknown;
  on?: (event: string, handler: (...args: unknown[]) => void) => void;
  off?: (event: string, handler: (...args: unknown[]) => void) => void;
  setData?: (data: unknown) => void;
  setLayout?: (layout: unknown, notRender?: boolean) => void;
  setMode?: (mode: "edit" | "readonly") => void;
  setTheme?: (theme: unknown, notRender?: boolean) => void;
  setThemeConfig?: (config: Record<string, unknown>, notRender?: boolean) => void;
  updateData?: (data: unknown) => void;
  updateConfig?: (config?: Record<string, unknown>) => void;
  view?: {
    scale?: number;
    enlarge?: () => void;
    narrow?: () => void;
    getTransformData?: () => unknown;
    setScale?: (scale: number, cx: number, cy: number) => void;
    fit?: () => void;
  };
  renderer?: {
    setRootNodeCenter?: () => void;
    textEdit?: {
      hideEditTextBox?: () => void;
      isShowTextEdit?: () => boolean;
    };
    clearActiveNodeList?: () => void;
    addNodeToActiveList?: (node: unknown, skipBeforeEvent?: boolean) => void;
    emitNodeActiveEvent?: (node?: unknown, activeNodeList?: unknown[]) => void;
    findNodeByUid?: (uid: string) => unknown;
    activeNodeList?: unknown[];
    lastActiveNodeList?: unknown[];
    root?: unknown;
    renderTree?: { _node?: unknown };
  };
};

export type SimpleMindMapCtor = new (options: Record<string, unknown>) => SimpleMindMapInstance;

export type SimpleMindMapRuntime = {
  MindMap: SimpleMindMapCtor;
  registeredPlugins: string[];
};

export type SimpleMindMapBridgeEventName =
  | "data_change"
  | "view_data_change"
  | "node_active"
  | "back_forward"
  | "scale"
  | "translate";

export type SimpleMindMapBridgeEvent = {
  type: SimpleMindMapBridgeEventName;
  args: unknown[];
  snapshot?: unknown;
  kernelRevision: number;
};

export type SimpleMindMapBridge = {
  instance: SimpleMindMapInstance;
  execCommand: (command: string, ...args: unknown[]) => SimpleMindMapSafeCommandResult;
  getSnapshot: () => unknown;
  refreshProjection?: (reason?: string) => Promise<void>;
  destroy: () => void;
};

export type SimpleMindMapSafeCommandResult =
  | { ok: true; command: string; result: unknown }
  | { ok: false; command: string; error: "unsupported_command" | "runtime_unavailable" | "command_failed" };

export const SIMPLE_MIND_MAP_REQUIRED_PLUGINS = [
  "Drag",
  "KeyboardNavigation",
  "Export",
  "Select",
  "AssociativeLine",
  "Search",
  "OuterFrame",
] as const;

export const SIMPLE_MIND_MAP_OPTIONAL_PLUGINS = [
  // RichText 会把纯文本节点转成 HTML 字符串；Phase 6.1 先保护 kernel 文本语义。
  "Scrollbar",
  "MiniMap",
  "Painter",
  "Formula",
] as const;

export const SIMPLE_MIND_MAP_BRIDGE_EVENTS: SimpleMindMapBridgeEventName[] = [
  "data_change",
  "view_data_change",
  "node_active",
  "back_forward",
  "scale",
  "translate",
];

export const SIMPLE_MIND_MAP_SAFE_COMMANDS = new Set([
  "BACK",
  "FORWARD",
  "INSERT_NODE",
  "INSERT_CHILD_NODE",
  "REMOVE_NODE",
  "DELETE_NODE",
  "ADD_GENERALIZATION",
  "ADD_OUTER_FRAME",
  "SET_NOTATION",
]);

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

const readString = (value: unknown, fallback: string): string => {
  if (typeof value === "string" && value.trim()) return value.trim();
  if (isRecord(value)) {
    const template = value.template;
    if (typeof template === "string" && template.trim()) return template.trim();
  }
  return fallback;
};

const readRecord = (value: unknown): Record<string, unknown> => {
  if (isRecord(value)) return value;
  return {};
};

const readThemeConfig = (value: unknown): Record<string, unknown> => {
  if (!isRecord(value) || Object.keys(value).length === 0) return createDefaultMindmapThemeConfig();
  return value;
};

const readViewData = (value: unknown): Record<string, unknown> | null => {
  if (!isRecord(value) || !isRecord(value.state)) return null;
  return {
    ...value,
    state: value.state,
    transform: isRecord(value.transform) ? value.transform : {},
  };
};

export const buildSimpleMindMapOptions = (input: {
  el: HTMLElement;
  projection: MindmapAdapterProjection;
  runtimeOptions?: Record<string, unknown>;
}): Record<string, unknown> => {
  const root = canonicalizeMindmapData(input.projection.root ?? defaultMindmapData);
  const config = readRecord(input.projection.config);
  const runtimeOptions = readRecord(input.runtimeOptions);
  return {
    ...config,
    ...runtimeOptions,
    el: input.el,
    data: root,
    fit:
      typeof runtimeOptions.fit === "boolean"
        ? runtimeOptions.fit
        : typeof config.fit === "boolean"
          ? config.fit
          : true,
    layout: readString(input.projection.layout, DEFAULT_MINDMAP_LAYOUT),
    theme: readString(input.projection.theme, DEFAULT_MINDMAP_THEME),
    themeConfig: readThemeConfig(input.projection.themeConfig),
    viewData: readViewData(input.projection.view),
    initRootNodePosition: ["center", "center"],
  };
};

const pluginImporters = {
  Drag: () => import("simple-mind-map/src/plugins/Drag.js"),
  KeyboardNavigation: () => import("simple-mind-map/src/plugins/KeyboardNavigation.js"),
  Export: () => import("simple-mind-map/src/plugins/Export.js"),
  Select: () => import("simple-mind-map/src/plugins/Select.js"),
  AssociativeLine: () => import("simple-mind-map/src/plugins/AssociativeLine.js"),
  Search: () => import("simple-mind-map/src/plugins/Search.js"),
  OuterFrame: () => import("simple-mind-map/src/plugins/OuterFrame.js"),
  Scrollbar: () => import("simple-mind-map/src/plugins/Scrollbar.js"),
  MiniMap: () => import("simple-mind-map/src/plugins/MiniMap.js"),
  Painter: () => import("simple-mind-map/src/plugins/Painter.js"),
  Formula: () => import("simple-mind-map/src/plugins/Formula.js"),
} satisfies Record<string, () => Promise<{ default: unknown }>>;

export type SimpleMindMapPluginName = keyof typeof pluginImporters;

export const defaultSimpleMindMapPluginNames = (): SimpleMindMapPluginName[] => [
  ...SIMPLE_MIND_MAP_REQUIRED_PLUGINS,
  ...SIMPLE_MIND_MAP_OPTIONAL_PLUGINS,
];

export const loadSimpleMindMapRuntime = async (
  pluginNames: SimpleMindMapPluginName[] = defaultSimpleMindMapPluginNames(),
): Promise<SimpleMindMapRuntime> => {
  if (typeof window === "undefined" || typeof document === "undefined") {
    throw new Error("simple_mind_map_browser_required");
  }

  const [{ default: MindMap }, pluginModules] = await Promise.all([
    import("simple-mind-map"),
    Promise.all(pluginNames.map(async (name) => [name, (await pluginImporters[name]()).default] as const)),
  ]);

  const MindMapCtor = MindMap as unknown as SimpleMindMapCtor & {
    hasPlugin?: (plugin: unknown) => number;
    usePlugin?: (plugin: unknown) => unknown;
  };

  const registeredPlugins: string[] = [];
  pluginModules.forEach(([name, plugin]) => {
    if (!plugin || typeof MindMapCtor.usePlugin !== "function") return;
    const registered =
      typeof MindMapCtor.hasPlugin === "function" ? MindMapCtor.hasPlugin(plugin) !== -1 : false;
    if (!registered) {
      MindMapCtor.usePlugin(plugin);
    }
    registeredPlugins.push(name);
  });

  return {
    MindMap: MindMapCtor,
    registeredPlugins,
  };
};

export const createSafeMindmapCommandExecutor =
  (instance: SimpleMindMapInstance) =>
  (command: string, ...args: unknown[]): SimpleMindMapSafeCommandResult => {
    if (!SIMPLE_MIND_MAP_SAFE_COMMANDS.has(command)) {
      return { ok: false, command, error: "unsupported_command" };
    }
    if (typeof instance.execCommand !== "function") {
      return { ok: false, command, error: "runtime_unavailable" };
    }
    try {
      return { ok: true, command, result: instance.execCommand(command, ...args) };
    } catch {
      return { ok: false, command, error: "command_failed" };
    }
  };

export const attachSimpleMindMapEventListeners = (input: {
  instance: SimpleMindMapInstance;
  projection: MindmapAdapterProjection;
  onEvent?: (event: SimpleMindMapBridgeEvent) => void;
}): (() => void) => {
  const cleanup: Array<() => void> = [];
  SIMPLE_MIND_MAP_BRIDGE_EVENTS.forEach((eventName) => {
    const handler = (...args: unknown[]) => {
      const snapshot =
        eventName === "data_change"
          ? input.instance.getData?.()
          : eventName === "view_data_change"
          ? input.instance.getData?.(true) ?? input.instance.getData?.()
          : undefined;
      input.onEvent?.({
        type: eventName,
        args,
        snapshot,
        kernelRevision: input.projection.kernelRevision,
      });
    };
    input.instance.on?.(eventName, handler);
    cleanup.push(() => input.instance.off?.(eventName, handler));
  });
  return () => cleanup.splice(0).forEach((dispose) => dispose());
};

export const createSimpleMindMapBridge = async (input: {
  el: HTMLElement;
  projection: MindmapAdapterProjection;
  pluginNames?: SimpleMindMapPluginName[];
  runtimeOptions?: Record<string, unknown>;
  mode?: "edit" | "readonly";
  onEvent?: (event: SimpleMindMapBridgeEvent) => void;
}): Promise<SimpleMindMapBridge> => {
  const runtime = await loadSimpleMindMapRuntime(input.pluginNames);
  const themeConfig = readThemeConfig(input.projection.themeConfig);
  const options = buildSimpleMindMapOptions({
    el: input.el,
    projection: input.projection,
    runtimeOptions: input.runtimeOptions,
  });
  const instance = new runtime.MindMap(options);
  instance.setThemeConfig?.(themeConfig);
  if (input.mode) instance.setMode?.(input.mode);

  const detachEvents = attachSimpleMindMapEventListeners({
    instance,
    projection: input.projection,
    onEvent: input.onEvent,
  });
  const execCommand = createSafeMindmapCommandExecutor(instance);

  return {
    instance,
    execCommand,
    getSnapshot: () => instance.getData?.(true) ?? instance.getData?.(),
    destroy: () => {
      instance.renderer?.textEdit?.hideEditTextBox?.();
      detachEvents();
      instance.destroy?.();
    },
  };
};

export const createFallbackMindmapAdapterProjection = (input: {
  mindmapId: string;
  root?: MindMapData;
  kernelRevision?: number;
}): MindmapAdapterProjection => ({
  schema: "mnote.mindmap.simple_mind_map_scene.v1",
  runtime: "simple-mind-map",
  root: input.root ?? defaultMindmapData,
  layout: DEFAULT_MINDMAP_LAYOUT,
  theme: DEFAULT_MINDMAP_THEME,
  themeConfig: createDefaultMindmapThemeConfig(),
  view: {},
  config: {},
  compatPayload: { source: "frontend-fallback", mindmapId: input.mindmapId },
  kernelRevision: input.kernelRevision ?? 1,
});
