/**
 * Chargeability calculator — pure functions for FTE-weighted chargeability reporting.
 *
 * Derives forecast chargeability from assignments + SAH.
 * No DB imports — all data passed in as arguments.
 */

// ─── Types ──────────────────────────────────────────────────────────────────

export interface ResourceForecastInput {
  fte: number;
  targetPercentage: number; // from management level group (0-1)
  /** Assignments active in the period, with their utilization category code. */
  assignments: AssignmentSlice[];
  /** SAH for the period (net working hours). */
  sah: number;
}

export interface AssignmentSlice {
  /** Hours per day for this assignment in the period. */
  hoursPerDay: number;
  /** Working days this assignment covers within the period. */
  workingDays: number;
  /** Utilization category code (e.g. "Chg", "BD", "MD&I", "M&O", "PD&R"). */
  categoryCode: string;
  /** Override total hours for this slice (e.g. when rules adjust chargeable hours).
   *  When set, used instead of hoursPerDay * workingDays. */
  totalChargeableHours?: number;
}

export interface ResourceForecast {
  chg: number;   // chargeability ratio (0-1)
  bd: number;    // business development ratio
  mdi: number;   // MD&I ratio
  mo: number;    // M&O ratio
  pdr: number;   // PD&R ratio
  absence: number; // absence ratio
  unassigned: number; // remaining ratio
}

export interface GroupChargeabilityInput {
  fte: number;
  chargeability: number; // 0-1
}

export interface GroupTargetInput {
  fte: number;
  targetPercentage: number; // 0-1
}

// ─── Functions ──────────────────────────────────────────────────────────────

/**
 * Derive forecast chargeability breakdown for a single resource in a period.
 * Returns ratios (0-1) for each utilization category.
 */
export function deriveResourceForecast(input: ResourceForecastInput): ResourceForecast {
  const { assignments, sah } = input;

  if (sah <= 0) {
    return { chg: 0, bd: 0, mdi: 0, mo: 0, pdr: 0, absence: 0, unassigned: 1 };
  }

  // Sum hours per category (use totalChargeableHours when available for rules-adjusted values)
  const categoryHours: Record<string, number> = {};
  for (const a of assignments) {
    const hours = a.totalChargeableHours ?? (a.hoursPerDay * a.workingDays);
    const key = a.categoryCode.toLowerCase();
    categoryHours[key] = (categoryHours[key] ?? 0) + hours;
  }

  const chgHours = categoryHours["chg"] ?? 0;
  const bdHours = categoryHours["bd"] ?? 0;
  const mdiHours = categoryHours["md&i"] ?? categoryHours["mdi"] ?? 0;
  const moHours = categoryHours["m&o"] ?? categoryHours["mo"] ?? 0;
  const pdrHours = categoryHours["pd&r"] ?? categoryHours["pdr"] ?? 0;
  const absenceHours = categoryHours["absence"] ?? 0;

  const totalAssigned = chgHours + bdHours + mdiHours + moHours + pdrHours + absenceHours;
  const unassignedHours = Math.max(0, sah - totalAssigned);

  return {
    chg: Math.min(1, chgHours / sah),
    bd: Math.min(1, bdHours / sah),
    mdi: Math.min(1, mdiHours / sah),
    mo: Math.min(1, moHours / sah),
    pdr: Math.min(1, pdrHours / sah),
    absence: Math.min(1, absenceHours / sah),
    unassigned: Math.min(1, unassignedHours / sah),
  };
}

/**
 * FTE-weighted chargeability for a group of resources.
 * Returns a ratio (0-1). Empty group returns 0.
 */
export function calculateGroupChargeability(resources: GroupChargeabilityInput[]): number {
  if (resources.length === 0) return 0;

  let weightedSum = 0;
  let totalFte = 0;
  for (const r of resources) {
    weightedSum += r.fte * r.chargeability;
    totalFte += r.fte;
  }

  return totalFte > 0 ? weightedSum / totalFte : 0;
}

/**
 * FTE-weighted target for a group of resources.
 * Returns a ratio (0-1). Empty group returns 0.
 */
export function calculateGroupTarget(resources: GroupTargetInput[]): number {
  if (resources.length === 0) return 0;

  let weightedSum = 0;
  let totalFte = 0;
  for (const r of resources) {
    weightedSum += r.fte * r.targetPercentage;
    totalFte += r.fte;
  }

  return totalFte > 0 ? weightedSum / totalFte : 0;
}

/**
 * Sum FTE for a group of resources.
 */
export function sumFte(resources: { fte: number }[]): number {
  return resources.reduce((sum, r) => sum + r.fte, 0);
}

/**
 * Get the start and end dates for a month (UTC).
 */
export function getMonthRange(year: number, month: number): { start: Date; end: Date } {
  const start = new Date(Date.UTC(year, month - 1, 1));
  const end = new Date(Date.UTC(year, month, 0)); // last day of month
  return { start, end };
}

/**
 * Generate a list of month keys (e.g. "2026-01", "2026-02") for a date range.
 */
export function getMonthKeys(startDate: Date, endDate: Date): string[] {
  const keys: string[] = [];
  const cursor = new Date(startDate);
  cursor.setUTCDate(1);

  while (cursor <= endDate) {
    const y = cursor.getUTCFullYear();
    const m = cursor.getUTCMonth() + 1;
    keys.push(`${y}-${String(m).padStart(2, "0")}`);
    cursor.setUTCMonth(cursor.getUTCMonth() + 1);
  }

  return keys;
}

/**
 * Count working days in a date range that overlap with an assignment period.
 * Excludes weekends. Does NOT exclude holidays (caller should adjust SAH for that).
 */
export function countWorkingDaysInOverlap(
  periodStart: Date,
  periodEnd: Date,
  assignmentStart: Date,
  assignmentEnd: Date,
): number {
  const start = new Date(Math.max(periodStart.getTime(), assignmentStart.getTime()));
  const end = new Date(Math.min(periodEnd.getTime(), assignmentEnd.getTime()));

  if (start > end) return 0;

  let count = 0;
  const cursor = new Date(start);
  cursor.setUTCHours(0, 0, 0, 0);
  const endNorm = new Date(end);
  endNorm.setUTCHours(0, 0, 0, 0);

  while (cursor <= endNorm) {
    const day = cursor.getUTCDay();
    if (day !== 0 && day !== 6) count++;
    cursor.setUTCDate(cursor.getUTCDate() + 1);
  }

  return count;
}
