import type { CategoryNode } from "./categoryTypes";

export interface CategoryOption {
  id: string;
  categoryName: string;
  categoryPath: string;
  depth: number;
  parentId: string | null;
  sortNo: number;
  isLeaf: boolean;
  displayLabel: string;
}

const CATEGORY_INDENT = "\u3000\u3000";

function compareStringIds(left: string, right: string): number {
  if (left.length !== right.length) return left.length - right.length;
  return left.localeCompare(right);
}

function sortedSiblings(
  nodes: readonly CategoryNode[],
): CategoryNode[] {
  return [...nodes].sort(
    (left, right) =>
      left.sortNo - right.sortNo || compareStringIds(left.id, right.id),
  );
}

export function flattenCategoryTree(
  tree: readonly CategoryNode[],
): CategoryNode[] {
  return sortedSiblings(tree).flatMap((node) => [
    node,
    ...flattenCategoryTree(node.children),
  ]);
}

export function flattenCategoryOptions(
  tree: readonly CategoryNode[],
  depth = 0,
): CategoryOption[] {
  return sortedSiblings(tree).flatMap((node) => [
    {
      id: node.id,
      categoryName: node.categoryName,
      categoryPath: node.categoryPath,
      depth,
      parentId: node.parentId,
      sortNo: node.sortNo,
      isLeaf: node.children.length === 0,
      displayLabel: `${CATEGORY_INDENT.repeat(depth)}${node.categoryName}`,
    },
    ...flattenCategoryOptions(node.children, depth + 1),
  ]);
}

export function findCategoryNode(
  tree: readonly CategoryNode[],
  id: string | null,
): CategoryNode | null {
  if (id === null) return null;
  return flattenCategoryTree(tree).find((node) => node.id === id) ?? null;
}

export function getCategoryBranchMainPlanCount(node: CategoryNode): number {
  return node.documentCount + node.children.reduce(
    (total, child) => total + getCategoryBranchMainPlanCount(child),
    0,
  );
}

export function getCategoryDescendantIds(node: CategoryNode): Set<string> {
  return new Set(flattenCategoryTree(node.children).map((child) => child.id));
}

export function getCategoryParentOptions(
  tree: readonly CategoryNode[],
  editingNode?: CategoryNode,
): CategoryOption[] {
  const options = flattenCategoryOptions(tree);
  if (!editingNode) return options;
  const excluded = getCategoryDescendantIds(editingNode);
  excluded.add(editingNode.id);
  return options.filter((node) => !excluded.has(node.id));
}

export function retainExistingCategoryIds(
  ids: ReadonlySet<string>,
  tree: readonly CategoryNode[],
): Set<string> {
  const existing = new Set(flattenCategoryTree(tree).map((node) => node.id));
  return new Set([...ids].filter((id) => existing.has(id)));
}
