/**
 * Standard Available Hours (SAH) calculator.
 *
 * SAH = net working time after deducting holidays and absences.
 * It is the denominator for chargeability calculations.
 */

import type { SpainScheduleRule } from "@capakraken/shared";

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

export interface SAHInput {
  /** Base daily working hours for the country (e.g. 8 for DE, 9 for IN). */
  dailyWorkingHours: number;
  /** Optional variable schedule rules (e.g. Spain). */
  scheduleRules?: SpainScheduleRule | null;
  /** Resource FTE factor (0.01 – 1.0). Reduces effective daily hours. */
  fte: number;
  /** Period start date (inclusive). */
  periodStart: Date;
  /** Period end date (inclusive). */
  periodEnd: Date;
  /** Public holiday dates within the period (ISO strings or Dates). */
  publicHolidays: (Date | string)[];
  /** Absence dates within the period (vacation, illness, other). */
  absenceDays: (Date | string)[];
}

export interface SAHResult {
  /** Total calendar days in the period. */
  calendarDays: number;
  /** Weekend days in the period. */
  weekendDays: number;
  /** Working days (calendar - weekends). */
  grossWorkingDays: number;
  /** Public holidays falling on working days. */
  publicHolidayDays: number;
  /** Absence days falling on working days (excluding holidays). */
  absenceDays: number;
  /** Net working days after holidays and absences. */
  netWorkingDays: number;
  /** Average effective hours per working day (after FTE scaling). */
  effectiveHoursPerDay: number;
  /** Total Standard Available Hours for the period. */
  standardAvailableHours: number;
}

// ─── Helpers ────────────────────────────────────────────────────────────────

function toISODate(d: Date | string): string {
  if (typeof d === "string") return d.slice(0, 10);
  return d.toISOString().slice(0, 10);
}

function isWeekend(date: Date): boolean {
  const day = date.getUTCDay();
  return day === 0 || day === 6;
}

/**
 * Get daily working hours for a specific date given optional Spain schedule rules.
 */
export function getDailyHours(
  date: Date,
  baseHours: number,
  scheduleRules?: SpainScheduleRule | null,
): number {
  if (!scheduleRules || scheduleRules.type !== "spain") {
    return baseHours;
  }

  const dayOfWeek = date.getUTCDay();

  // Fridays always use fridayHours
  if (dayOfWeek === 5) {
    return scheduleRules.fridayHours;
  }

  // Check if date falls in summer period
  const month = date.getUTCMonth() + 1;
  const day = date.getUTCDate();
  const mmdd = `${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;

  if (mmdd >= scheduleRules.summerPeriod.from && mmdd <= scheduleRules.summerPeriod.to) {
    return scheduleRules.summerHours;
  }

  return scheduleRules.regularHours;
}

// ─── Calculator ─────────────────────────────────────────────────────────────

export function calculateSAH(input: SAHInput): SAHResult {
  const { dailyWorkingHours, scheduleRules, fte, periodStart, periodEnd, publicHolidays, absenceDays } = input;

  const holidaySet = new Set(publicHolidays.map(toISODate));
  const absenceSet = new Set(absenceDays.map(toISODate));

  let calendarDays = 0;
  let weekendDays = 0;
  let publicHolidayCount = 0;
  let absenceCount = 0;
  let totalHoursOnWorkingDays = 0;
  let netWorkingDays = 0;

  const cursor = new Date(periodStart);
  cursor.setUTCHours(0, 0, 0, 0);
  const end = new Date(periodEnd);
  end.setUTCHours(0, 0, 0, 0);

  while (cursor <= end) {
    calendarDays++;
    const iso = cursor.toISOString().slice(0, 10);

    if (isWeekend(cursor)) {
      weekendDays++;
    } else if (holidaySet.has(iso)) {
      publicHolidayCount++;
    } else if (absenceSet.has(iso)) {
      absenceCount++;
    } else {
      // This is a net working day
      const hoursForDay = getDailyHours(cursor, dailyWorkingHours, scheduleRules);
      totalHoursOnWorkingDays += hoursForDay * fte;
      netWorkingDays++;
    }

    cursor.setUTCDate(cursor.getUTCDate() + 1);
  }

  const grossWorkingDays = calendarDays - weekendDays;
  const effectiveHoursPerDay = netWorkingDays > 0 ? totalHoursOnWorkingDays / netWorkingDays : dailyWorkingHours * fte;

  return {
    calendarDays,
    weekendDays,
    grossWorkingDays,
    publicHolidayDays: publicHolidayCount,
    absenceDays: absenceCount,
    netWorkingDays,
    effectiveHoursPerDay: Math.round(effectiveHoursPerDay * 100) / 100,
    standardAvailableHours: Math.round(totalHoursOnWorkingDays * 100) / 100,
  };
}
