import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Transaction } from "../generated/kysely-tailordb";
import {
  CategoryNotFoundError,
  ParentNotFoundError,
  CircularReferenceError,
  MaxDepthExceededError,
} from "../lib/errors.generated";
import { calculateCategoryDepth } from "../query/calculateCategoryDepth.generated";

export interface MoveProductCategoryInput {
  id: string;
  newParentId: string | null;
}

/**
 * Function: moveProductCategory
 *
 * Reparents a category node. Moving to null promotes to root.
 */
export async function run(
  db: Transaction,
  input: MoveProductCategoryInput,
  ctx: CommandContext,
  config: { maxDepth: number },
) {
  const { maxDepth } = config;
  const { id, newParentId } = input;

  const category = await db
    .selectFrom("ProductCategory")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();
  if (!category) {
    return err(new CategoryNotFoundError(id));
  }

  // Promote to root
  if (newParentId === null) {
    const moved = await db
      .updateTable("ProductCategory")
      .set({ parentId: null })
      .where("id", "=", id)
      .returningAll()
      .executeTakeFirstOrThrow();
    return ok({ category: moved });
  }

  // Check new parent exists
  const newParent = await db
    .selectFrom("ProductCategory")
    .select("id")
    .where("id", "=", newParentId)
    .executeTakeFirst();
  if (!newParent) {
    return err(new ParentNotFoundError(newParentId));
  }

  // Check circular reference
  const isDescendant = await isAncestorOf(db, id, newParentId);
  if (isDescendant) {
    return err(new CircularReferenceError(`${id} -> ${newParentId}`));
  }

  // Check depth limit: need to calculate subtree depth of moved node
  const depthResult = await calculateCategoryDepth(db, { categoryId: newParentId }, ctx);
  if (!depthResult.ok) return err(new ParentNotFoundError(newParentId));
  const newParentDepth = depthResult.value.depth;
  // Calculate subtree depth of the node being moved
  const subtreeDepth = await calculateSubtreeDepth(db, id);
  const totalDepth = newParentDepth + subtreeDepth;

  if (totalDepth > maxDepth) {
    return err(new MaxDepthExceededError(`${totalDepth} exceeds ${maxDepth}`));
  }

  const moved = await db
    .updateTable("ProductCategory")
    .set({ parentId: newParentId })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ category: moved });
}

/** Checks if `ancestorId` is an ancestor of `descendantId` by walking up the tree. */
async function isAncestorOf(
  db: Transaction,
  ancestorId: string,
  descendantId: string,
): Promise<boolean> {
  let currentId: string | null = descendantId;
  while (currentId) {
    if (currentId === ancestorId) return true;
    const parent = await db
      .selectFrom("ProductCategory")
      .select("parentId")
      .where("id", "=", currentId)
      .executeTakeFirst();
    currentId = parent?.parentId ?? null;
  }
  return false;
}

async function calculateSubtreeDepth(db: Transaction, nodeId: string): Promise<number> {
  const children = await db
    .selectFrom("ProductCategory")
    .select("id")
    .where("parentId", "=", nodeId)
    .execute();

  if (children.length === 0) return 1;

  let maxChildDepth = 0;
  for (const child of children) {
    const childDepth = await calculateSubtreeDepth(db, child.id);
    if (childDepth > maxChildDepth) maxChildDepth = childDepth;
  }

  return 1 + maxChildDepth;
}
