/**
 * Reject-reason clustering for the overview dashboard.
 *
 * Raw reject reasons embed object-specific details after a colon
 * (e.g. "manifest is not valid at validation_time: this_update=2023-…"),
 * which scatters one logical cause across many rows. Grouping by the text
 * before the first colon collapses them back into a single cluster.
 */

/** Cluster label for a raw reject reason: text before the first colon. */
export function reasonClusterKey(reason: string): string {
  const idx = reason.indexOf(":");
  return (idx === -1 ? reason : reason.slice(0, idx)).trim();
}

export interface ReasonCluster {
  /** Cluster label (prefix before the first colon, or the whole reason). */
  key: string;
  /** Total rejected objects across all reasons in the cluster. */
  count: number;
  /** Distinct raw reasons merged into the cluster. */
  variants: number;
}

/** Aggregate a reason→count map into clusters, sorted by count desc. */
export function clusterReasons(map: Record<string, number>, limit = 8): ReasonCluster[] {
  const clusters = new Map<string, { count: number; variants: number }>();
  for (const [reason, count] of Object.entries(map)) {
    const key = reasonClusterKey(reason);
    const entry = clusters.get(key) ?? { count: 0, variants: 0 };
    entry.count += count;
    entry.variants += 1;
    clusters.set(key, entry);
  }
  return [...clusters.entries()]
    .map(([key, { count, variants }]) => ({ key, count, variants }))
    .sort((a, b) => b.count - a.count || a.key.localeCompare(b.key))
    .slice(0, limit);
}
