export type DocumentMoveOrderDocument = {
  id: string;
  parent_id?: string | null;
  sort_order?: number | null;
  created_at?: string | null;
};

export type DocumentMoveOrderPatch = {
  documentId: string;
  parentId: string | null;
  sortOrder: number;
  moved: boolean;
};

export type DocumentMoveOrderPlan = {
  documentId: string;
  fromParentId: string | null;
  toParentId: string | null;
  requestedSortOrder: number;
  normalizedSortOrder: number;
  patches: DocumentMoveOrderPatch[];
};

export type DocumentMoveWriteOperation = DocumentMoveOrderPlan & {
  family: "tree";
  schema: "mnote.tree.write_operation";
  schemaVersion: 1;
  operation: "tree.subtree.move.write";
  workspaceId: string;
};

function normalizeParentId(value: string | null | undefined): string | null {
  const trimmed = typeof value === "string" ? value.trim() : "";
  return trimmed.length > 0 ? trimmed : null;
}

function normalizeSortOrder(value: number | null | undefined): number {
  if (typeof value !== "number" || !Number.isFinite(value)) {
    return Number.MAX_SAFE_INTEGER;
  }
  return Math.floor(value);
}

function compareDocumentMoveOrder(a: DocumentMoveOrderDocument, b: DocumentMoveOrderDocument): number {
  const orderA = normalizeSortOrder(a.sort_order);
  const orderB = normalizeSortOrder(b.sort_order);
  if (orderA !== orderB) return orderA - orderB;
  const createdA = String(a.created_at ?? "");
  const createdB = String(b.created_at ?? "");
  if (createdA !== createdB) return createdA.localeCompare(createdB);
  return a.id.localeCompare(b.id);
}

function clampMoveIndex(raw: number, max: number): number {
  const value = Number.isFinite(raw) ? Math.floor(raw) : 0;
  if (value < 0) return 0;
  if (value > max) return max;
  return value;
}

function appendOrderPatches(
  patches: DocumentMoveOrderPatch[],
  ordered: DocumentMoveOrderDocument[],
  parentId: string | null,
  movedDocumentId: string,
) {
  ordered.forEach((document, index) => {
    const moved = document.id === movedDocumentId;
    if (normalizeParentId(document.parent_id) === parentId && normalizeSortOrder(document.sort_order) === index && !moved) {
      return;
    }

    patches.push({
      documentId: document.id,
      parentId,
      sortOrder: index,
      moved,
    });
  });
}

export function buildDocumentMoveOrderPlanFromDocuments(input: {
  documents: readonly DocumentMoveOrderDocument[];
  documentId: string;
  parentId: string | null;
  sortOrder: number;
}): DocumentMoveOrderPlan {
  const source = input.documents.find((document) => document.id === input.documentId);
  if (!source) {
    throw new Error("源页面不存在或无权限");
  }

  const fromParentId = normalizeParentId(source.parent_id);
  const toParentId = normalizeParentId(input.parentId);
  const siblingsByParent = new Map<string | null, DocumentMoveOrderDocument[]>();
  input.documents.forEach((document) => {
    const parentId = normalizeParentId(document.parent_id);
    const bucket = siblingsByParent.get(parentId);
    if (bucket) bucket.push(document);
    else siblingsByParent.set(parentId, [document]);
  });
  siblingsByParent.forEach((siblings) => siblings.sort(compareDocumentMoveOrder));

  const patches: DocumentMoveOrderPatch[] = [];
  let normalizedSortOrder = 0;

  if (fromParentId === toParentId) {
    const siblings = [...(siblingsByParent.get(toParentId) ?? [])].filter((document) => document.id !== input.documentId);
    normalizedSortOrder = clampMoveIndex(input.sortOrder, siblings.length);
    siblings.splice(normalizedSortOrder, 0, source);
    appendOrderPatches(patches, siblings, toParentId, input.documentId);
  } else {
    const oldSiblings = [...(siblingsByParent.get(fromParentId) ?? [])].filter((document) => document.id !== input.documentId);
    appendOrderPatches(patches, oldSiblings, fromParentId, input.documentId);

    const newSiblings = [...(siblingsByParent.get(toParentId) ?? [])].filter((document) => document.id !== input.documentId);
    normalizedSortOrder = clampMoveIndex(input.sortOrder, newSiblings.length);
    newSiblings.splice(normalizedSortOrder, 0, source);
    appendOrderPatches(patches, newSiblings, toParentId, input.documentId);
  }

  return {
    documentId: input.documentId,
    fromParentId,
    toParentId,
    requestedSortOrder: input.sortOrder,
    normalizedSortOrder,
    patches,
  };
}

export function normalizeDocumentMoveOrderPlan(value: unknown): DocumentMoveOrderPlan | null {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return null;
  }
  const record = value as Partial<DocumentMoveOrderPlan>;
  if (
    typeof record.documentId !== "string" ||
    typeof record.requestedSortOrder !== "number" ||
    typeof record.normalizedSortOrder !== "number" ||
    !Array.isArray(record.patches)
  ) {
    return null;
  }
  return {
    documentId: record.documentId,
    fromParentId: normalizeParentId(record.fromParentId),
    toParentId: normalizeParentId(record.toParentId),
    requestedSortOrder: record.requestedSortOrder,
    normalizedSortOrder: record.normalizedSortOrder,
    patches: record.patches.map((patch) => {
      const item = patch as Partial<DocumentMoveOrderPatch>;
      return {
        documentId: String(item.documentId ?? ""),
        parentId: normalizeParentId(item.parentId),
        sortOrder: typeof item.sortOrder === "number" && Number.isFinite(item.sortOrder) ? Math.floor(item.sortOrder) : -1,
        moved: Boolean(item.moved),
      };
    }),
  };
}

export function normalizeDocumentMoveWriteOperation(value: unknown): DocumentMoveWriteOperation | null {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return null;
  }
  const record = value as Partial<DocumentMoveWriteOperation>;
  if (
    record.family !== "tree" ||
    record.schema !== "mnote.tree.write_operation" ||
    record.schemaVersion !== 1 ||
    record.operation !== "tree.subtree.move.write" ||
    typeof record.workspaceId !== "string" ||
    !record.workspaceId.trim()
  ) {
    return null;
  }
  const normalizedPlan = normalizeDocumentMoveOrderPlan(value);
  if (!normalizedPlan) {
    return null;
  }
  return {
    family: "tree",
    schema: "mnote.tree.write_operation",
    schemaVersion: 1,
    operation: "tree.subtree.move.write",
    workspaceId: record.workspaceId.trim(),
    ...normalizedPlan,
  };
}

export function assertDocumentMoveWriteOperationMatches(expected: unknown, actual: DocumentMoveOrderPlan): DocumentMoveOrderPlan {
  const normalizedExpected = normalizeDocumentMoveWriteOperation(expected);
  if (!normalizedExpected) {
    throw new Error("Rust move write operation 与 Convex 当前排序状态不一致");
  }

  const { family: _family, schema: _schema, schemaVersion: _schemaVersion, operation: _operation, workspaceId: _workspaceId, ...expectedPlan } =
    normalizedExpected;
  if (JSON.stringify(expectedPlan) !== JSON.stringify(actual)) {
    throw new Error("Rust move write operation 与 Convex 当前排序状态不一致");
  }

  return expectedPlan;
}
