import "server-only";

export type NodeRef = {
  kind: "pdf" | "docx" | "pptx" | "url";
  assetId?: string;
  fileUrl?: string;
  page?: number; // 1-based
  slide?: number; // 1-based
  title?: string;
  snippet?: string;
};

export type MindmapNodeData = {
  uid?: string;
  text?: string;
  hyperlink?: string;
  note?: string;
  refs?: NodeRef[];
  [k: string]: unknown;
};

export type MindmapTreeNode = {
  data: MindmapNodeData;
  children?: MindmapTreeNode[];
  [k: string]: unknown;
};

export type MindmapOp =
  | {
      op: "addChild";
      parentUid: string;
      node: { uid?: string; text: string; hyperlink?: string; refs?: NodeRef[]; note?: string };
    }
  | {
      op: "addSiblingAfter";
      targetUid: string;
      node: { uid?: string; text: string; hyperlink?: string; refs?: NodeRef[]; note?: string };
    }
  | { op: "updateText"; uid: string; text: string }
  | { op: "setHyperlink"; uid: string; hyperlink: string | null }
  | { op: "setRefs"; uid: string; refs: NodeRef[] }
  | { op: "appendNote"; uid: string; markdown: string }
  | { op: "deleteNode"; uid: string };

export type MindmapOpErrorKind = "rejected" | "failed";

const createUid = () => {
  const anyCrypto = globalThis.crypto as unknown as { randomUUID?: () => string } | undefined;
  if (anyCrypto?.randomUUID) return anyCrypto.randomUUID();
  return Math.random().toString(36).slice(2);
};

export const ensureMindmapUids = (root: MindmapTreeNode) => {
  const walk = (node: MindmapTreeNode) => {
    if (!node.data) node.data = {};
    if (!node.data.uid) node.data.uid = createUid();
    if (typeof node.data.text !== "string") node.data.text = String(node.data.text ?? "新节点");
    if (!Array.isArray(node.children)) node.children = [];
    node.children.forEach(walk);
  };
  walk(root);
  return root;
};

type Indexed = {
  node: MindmapTreeNode;
  parent: MindmapTreeNode | null;
  index: number;
};

const buildIndex = (root: MindmapTreeNode) => {
  const map = new Map<string, Indexed>();
  const walk = (node: MindmapTreeNode, parent: MindmapTreeNode | null) => {
    const uid = String(node?.data?.uid || "");
    if (uid) map.set(uid, { node, parent, index: -1 });
    const children = Array.isArray(node.children) ? node.children : [];
    children.forEach((child, idx) => {
      const cuid = String(child?.data?.uid || "");
      if (cuid) map.set(cuid, { node: child, parent: node, index: idx });
      walk(child, node);
    });
  };
  walk(root, null);
  return map;
};

const safeUrlOrNull = (value: unknown) => {
  const s = typeof value === "string" ? value.trim() : "";
  if (!s) return null;
  try {
    const u = new URL(s);
    if (u.protocol !== "http:" && u.protocol !== "https:") return null;
    return u.toString();
  } catch {
    return null;
  }
};

export const classifyMindmapOpErrors = (errors: string[]): MindmapOpErrorKind | null => {
  if (!Array.isArray(errors) || errors.length === 0) return null;
  const rejectedPatterns = [
    "无效 op",
    "找不到",
    "不能删除根节点",
    "不支持的 op",
  ];
  const rejectedOnly = errors.every((error) =>
    rejectedPatterns.some((pattern) => String(error || "").includes(pattern)),
  );
  return rejectedOnly ? "rejected" : "failed";
};

export const applyMindmapOps = (
  raw: unknown,
  ops: MindmapOp[],
): { data: MindmapTreeNode; applied: number; errors: string[] } => {
  const root = (raw && typeof raw === "object" ? (raw as MindmapTreeNode) : null) ?? {
    data: { text: "中心主题" },
    children: [],
  };
  ensureMindmapUids(root);

  const errors: string[] = [];
  let applied = 0;

  for (const op of ops) {
    ensureMindmapUids(root);
    const index = buildIndex(root);

    if (!op || typeof op !== "object" || !("op" in op)) {
      errors.push("无效 op");
      continue;
    }

    if (op.op === "addChild") {
      const parent = index.get(op.parentUid)?.node ?? null;
      if (!parent) {
        errors.push(`addChild: 找不到 parentUid=${op.parentUid}`);
        continue;
      }
      if (!Array.isArray(parent.children)) parent.children = [];
      const uid = op.node.uid || createUid();
      parent.children.push({
        data: {
          uid,
          text: String(op.node.text ?? "新节点"),
          ...(op.node.note ? { note: String(op.node.note) } : {}),
          ...(op.node.refs ? { refs: op.node.refs } : {}),
          ...(op.node.hyperlink ? { hyperlink: safeUrlOrNull(op.node.hyperlink) ?? undefined } : {}),
        },
        children: [],
      });
      applied += 1;
      continue;
    }

    if (op.op === "addSiblingAfter") {
      const hit = index.get(op.targetUid);
      if (!hit?.parent) {
        errors.push(`addSiblingAfter: 找不到 targetUid=${op.targetUid} 或无父节点（不能对根节点加同级）`);
        continue;
      }
      if (!Array.isArray(hit.parent.children)) hit.parent.children = [];
      const uid = op.node.uid || createUid();
      const insertAt = Math.max(0, Math.min(hit.parent.children.length, hit.index + 1));
      hit.parent.children.splice(insertAt, 0, {
        data: {
          uid,
          text: String(op.node.text ?? "新节点"),
          ...(op.node.note ? { note: String(op.node.note) } : {}),
          ...(op.node.refs ? { refs: op.node.refs } : {}),
          ...(op.node.hyperlink ? { hyperlink: safeUrlOrNull(op.node.hyperlink) ?? undefined } : {}),
        },
        children: [],
      });
      applied += 1;
      continue;
    }

    if (op.op === "updateText") {
      const hit = index.get(op.uid)?.node ?? null;
      if (!hit) {
        errors.push(`updateText: 找不到 uid=${op.uid}`);
        continue;
      }
      hit.data = hit.data || {};
      hit.data.text = String(op.text ?? "");
      applied += 1;
      continue;
    }

    if (op.op === "setHyperlink") {
      const hit = index.get(op.uid)?.node ?? null;
      if (!hit) {
        errors.push(`setHyperlink: 找不到 uid=${op.uid}`);
        continue;
      }
      hit.data = hit.data || {};
      const url = safeUrlOrNull(op.hyperlink);
      if (!url) {
        delete (hit.data as any).hyperlink;
      } else {
        hit.data.hyperlink = url;
      }
      applied += 1;
      continue;
    }

    if (op.op === "setRefs") {
      const hit = index.get(op.uid)?.node ?? null;
      if (!hit) {
        errors.push(`setRefs: 找不到 uid=${op.uid}`);
        continue;
      }
      hit.data = hit.data || {};
      hit.data.refs = Array.isArray(op.refs) ? op.refs : [];
      applied += 1;
      continue;
    }

    if (op.op === "appendNote") {
      const hit = index.get(op.uid)?.node ?? null;
      if (!hit) {
        errors.push(`appendNote: 找不到 uid=${op.uid}`);
        continue;
      }
      hit.data = hit.data || {};
      const prev = typeof hit.data.note === "string" ? hit.data.note : "";
      const next = String(op.markdown ?? "");
      hit.data.note = prev ? `${prev}\n\n${next}` : next;
      applied += 1;
      continue;
    }

    if (op.op === "deleteNode") {
      const hit = index.get(op.uid);
      if (!hit) {
        errors.push(`deleteNode: 找不到 uid=${op.uid}`);
        continue;
      }
      if (!hit.parent) {
        errors.push("deleteNode: 不能删除根节点");
        continue;
      }
      if (!Array.isArray(hit.parent.children)) hit.parent.children = [];
      hit.parent.children = hit.parent.children.filter((c) => String(c?.data?.uid || "") !== op.uid);
      applied += 1;
      continue;
    }

    errors.push(`不支持的 op: ${(op as any).op}`);
  }

  ensureMindmapUids(root);
  return { data: root, applied, errors };
};
