import type { MindmapCompatPayloadPatch, MindmapKernelCommand } from "./mindmap-command-diff";

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

type MetadataOnlyMindmapCommand =
  | Extract<MindmapKernelCommand, { type: "setLayout" | "setTheme" | "patchView" }>
  | MindmapCompatPayloadPatch;

type LocallyApplicableMindmapCommand =
  | Exclude<MindmapKernelCommand, { type: "moveNode" }>
  | MindmapCompatPayloadPatch;

type ApplyMetadataOnlyMindmapCommandsResult = {
  applied: number;
  data: Record<string, unknown>;
  errors: string[];
};

type ApplyMindmapCommandsLocallyResult = ApplyMetadataOnlyMindmapCommandsResult;

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

const cloneJson = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;

const ensureMindmapBlob = (value: unknown): Record<string, unknown> => {
  const next = isRecord(value) ? cloneJson(value) : cloneJson(defaultMindmapData);
  if (!isRecord(next.data)) next.data = { text: "中心主题" };
  if (!Array.isArray(next.children)) next.children = [];
  return next;
};

const mergeJsonObject = (base: unknown, patch: unknown): unknown => {
  if (!isRecord(base)) return cloneJson(patch);
  if (!isRecord(patch)) return cloneJson(patch);
  const next = { ...base };
  Object.entries(patch).forEach(([key, value]) => {
    if (value === null) {
      delete next[key];
      return;
    }
    next[key] = isRecord(next[key]) && isRecord(value) ? mergeJsonObject(next[key], value) : cloneJson(value);
  });
  return next;
};

const setNestedPath = (root: Record<string, unknown>, path: string, value: unknown): boolean => {
  const segments = path.split(".").filter(Boolean);
  if (segments.length === 0) return false;
  let current: Record<string, unknown> = root;
  for (const segment of segments.slice(0, -1)) {
    if (!isRecord(current[segment])) current[segment] = {};
    current = current[segment] as Record<string, unknown>;
  }
  current[segments[segments.length - 1]] = cloneJson(value);
  return true;
};

const findMindmapNodeByUid = (node: unknown, uid: string): Record<string, unknown> | null => {
  if (!isRecord(node)) return null;
  const data = isRecord(node.data) ? node.data : null;
  if (typeof data?.uid === "string" && data.uid === uid) return node;
  const children = Array.isArray(node.children) ? node.children : [];
  for (const child of children) {
    const found = findMindmapNodeByUid(child, uid);
    if (found) return found;
  }
  return null;
};

const findMindmapNodeContainerByUid = (
  nodes: unknown[],
  uid: string,
): { parentChildren: Record<string, unknown>[]; index: number } | null => {
  for (let index = 0; index < nodes.length; index += 1) {
    const candidate = nodes[index];
    if (!isRecord(candidate)) continue;
    const data = isRecord(candidate.data) ? candidate.data : null;
    if (typeof data?.uid === "string" && data.uid === uid) {
      return {
        parentChildren: nodes as Record<string, unknown>[],
        index,
      };
    }
    const children = Array.isArray(candidate.children) ? candidate.children : [];
    const found = findMindmapNodeContainerByUid(children, uid);
    if (found) return found;
  }
  return null;
};

const createMindmapNodeRecord = (node: {
  uid?: string;
  text: string;
  hyperlink?: string;
  note?: string;
  refs?: unknown[];
}): Record<string, unknown> => ({
  data: {
    uid: node.uid ?? `node_${Date.now().toString(36)}`,
    text: node.text,
    ...(typeof node.hyperlink === "string" ? { hyperlink: node.hyperlink } : {}),
    ...(typeof node.note === "string" ? { note: node.note } : {}),
    ...(Array.isArray(node.refs) ? { refs: cloneJson(node.refs) } : {}),
  },
  children: [],
});

const applyCompatPayloadPatch = (root: Record<string, unknown>, command: MindmapCompatPayloadPatch): boolean => {
  const path = String(command.path || "").trim();
  if (!path) return false;

  if (path.startsWith("root.data.")) {
    const field = path.slice("root.data.".length);
    const rootData = isRecord(root.data) ? root.data : (root.data = {});
    return setNestedPath(rootData as Record<string, unknown>, field, command.value);
  }

  if (path.startsWith("nodes.")) {
    const [, uid, scope, ...rest] = path.split(".");
    if (!uid || scope !== "data" || rest.length === 0) return false;
    const node = findMindmapNodeByUid(root, uid);
    if (!node) return false;
    const nodeData = isRecord(node.data) ? node.data : (node.data = {});
    return setNestedPath(nodeData as Record<string, unknown>, rest.join("."), command.value);
  }

  const compatPayload = isRecord(root.compatPayload) ? root.compatPayload : (root.compatPayload = {});
  return setNestedPath(compatPayload as Record<string, unknown>, path, command.value);
};

export const isMetadataOnlyMindmapCommandSet = (commands: unknown[]): commands is MetadataOnlyMindmapCommand[] =>
  Array.isArray(commands) &&
  commands.every((command) => {
    if (!isRecord(command) || typeof command.type !== "string") return false;
    return ["setLayout", "setTheme", "patchView", "compatPayloadPatch"].includes(command.type);
  });

export const isLocallyApplicableMindmapCommandSet = (
  commands: unknown[],
): commands is LocallyApplicableMindmapCommand[] =>
  Array.isArray(commands) &&
  commands.every((command) => {
    if (!isRecord(command) || typeof command.type !== "string") return false;
    return [
      "updateText",
      "insertChild",
      "insertSiblingAfter",
      "deleteNode",
      "setLayout",
      "setTheme",
      "patchView",
      "compatPayloadPatch",
    ].includes(command.type);
  });

export const applyMetadataOnlyMindmapCommands = (
  currentData: unknown,
  commands: MetadataOnlyMindmapCommand[],
): ApplyMetadataOnlyMindmapCommandsResult => {
  const nextData = ensureMindmapBlob(currentData);
  const errors: string[] = [];
  let applied = 0;

  commands.forEach((command) => {
    if (command.type === "setLayout") {
      nextData.layout = cloneJson(command.layout);
      applied += 1;
      return;
    }
    if (command.type === "setTheme") {
      nextData.theme = cloneJson(command.theme);
      if (command.themeConfig !== undefined && command.themeConfig !== null) {
        nextData.themeConfig = cloneJson(command.themeConfig);
      }
      applied += 1;
      return;
    }
    if (command.type === "patchView") {
      nextData.view = mergeJsonObject(nextData.view ?? {}, command.patch) as Record<string, unknown>;
      applied += 1;
      return;
    }
    if (command.type === "compatPayloadPatch") {
      if (applyCompatPayloadPatch(nextData, command)) {
        applied += 1;
      } else {
        errors.push(`无法应用 compatPayloadPatch:${command.path}`);
      }
    }
  });

  return {
    applied,
    data: nextData,
    errors,
  };
};

export const applyMindmapCommandsLocally = (
  currentData: unknown,
  commands: LocallyApplicableMindmapCommand[],
): ApplyMindmapCommandsLocallyResult => {
  const nextData = ensureMindmapBlob(currentData);
  const errors: string[] = [];
  let applied = 0;

  commands.forEach((command) => {
    if (command.type === "setLayout" || command.type === "setTheme" || command.type === "patchView" || command.type === "compatPayloadPatch") {
      const result = applyMetadataOnlyMindmapCommands(nextData, [command]);
      Object.assign(nextData, result.data);
      applied += result.applied;
      errors.push(...result.errors);
      return;
    }

    if (command.type === "updateText") {
      const node = findMindmapNodeByUid(nextData, command.nodeId);
      if (!node) {
        errors.push(`未找到节点:${command.nodeId}`);
        return;
      }
      const data = isRecord(node.data) ? node.data : (node.data = {});
      data.text = command.text;
      applied += 1;
      return;
    }

    if (command.type === "insertChild") {
      const parent = findMindmapNodeByUid(nextData, command.parentNodeId);
      if (!parent) {
        errors.push(`未找到父节点:${command.parentNodeId}`);
        return;
      }
      const children = Array.isArray(parent.children) ? parent.children : (parent.children = []);
      children.push(createMindmapNodeRecord(command.node));
      applied += 1;
      return;
    }

    if (command.type === "insertSiblingAfter") {
      const container = findMindmapNodeContainerByUid([nextData], command.targetNodeId);
      if (!container) {
        errors.push(`未找到同级节点:${command.targetNodeId}`);
        return;
      }
      if (container.parentChildren[container.index] === nextData) {
        errors.push("根节点不支持插入同级节点");
        return;
      }
      container.parentChildren.splice(container.index + 1, 0, createMindmapNodeRecord(command.node));
      applied += 1;
      return;
    }

    if (command.type === "deleteNode") {
      const container = findMindmapNodeContainerByUid([nextData], command.nodeId);
      if (!container) {
        errors.push(`未找到删除节点:${command.nodeId}`);
        return;
      }
      if (container.parentChildren[container.index] === nextData) {
        errors.push("根节点不支持删除");
        return;
      }
      container.parentChildren.splice(container.index, 1);
      applied += 1;
    }
  });

  return {
    applied,
    data: nextData,
    errors,
  };
};
