export interface PerformanceBudgets {
  routeJsBytes?: number;
  routeCssBytes?: number;
  htmlBytes?: number;
  imageBytes?: number;
  hydrationMs?: number;
  serverRenderMs?: number;
  /** Largest Contentful Paint in milliseconds. Recommended <= 2500. */
  lcpMs?: number;
  /** Interaction to Next Paint in milliseconds. Recommended <= 200. */
  inpMs?: number;
  /** Cumulative Layout Shift score. Recommended <= 0.1. */
  cls?: number;
  /** Time to First Byte in milliseconds. */
  ttfbMs?: number;
  /** Longest main-thread task in milliseconds. Recommended <= 50. */
  longTaskMs?: number;
  /** Number of client hydration boundaries on the route. */
  hydratedComponents?: number;
  /** Total request count for the initial navigation. */
  requests?: number;
}

export interface PerformanceMeasurement {
  routeJsBytes?: number;
  routeCssBytes?: number;
  htmlBytes?: number;
  imageBytes?: number;
  hydrationMs?: number;
  serverRenderMs?: number;
  lcpMs?: number;
  inpMs?: number;
  cls?: number;
  ttfbMs?: number;
  longTaskMs?: number;
  hydratedComponents?: number;
  requests?: number;
}

export interface BudgetViolation {
  metric: keyof PerformanceBudgets;
  budget: number;
  actual: number;
  overBy: number;
}

export const recommendedWebBudgets: Readonly<PerformanceBudgets> = Object.freeze({
  lcpMs: 2500,
  inpMs: 200,
  cls: 0.1,
  longTaskMs: 50,
  routeJsBytes: 50 * 1024,
  routeCssBytes: 25 * 1024,
});

export function checkPerformanceBudgets(
  budgets: PerformanceBudgets,
  measurement: PerformanceMeasurement,
): BudgetViolation[] {
  const violations: BudgetViolation[] = [];
  for (const metric of Object.keys(budgets) as Array<keyof PerformanceBudgets>) {
    const budget = budgets[metric];
    const actual = measurement[metric];
    if (budget === undefined || actual === undefined || actual <= budget) continue;
    violations.push({ metric, budget, actual, overBy: actual - budget });
  }
  return violations;
}
