import { getCalendarHolidayStrings, toIsoDate } from "./holiday-availability.js";

type VacationSpan = {
  startDate: Date;
  endDate: Date;
  isHalfDay: boolean;
};

type HolidayContext = {
  countryCode?: string | null | undefined;
  federalState?: string | null | undefined;
  metroCityName?: string | null | undefined;
  calendarHolidayStrings?: string[] | undefined;
  publicHolidayStrings?: string[] | undefined;
};

type CountVacationChargeableDaysInput = HolidayContext & {
  vacation: VacationSpan;
  periodStart?: Date | undefined;
  periodEnd?: Date | undefined;
};

function clampToDay(value: Date): Date {
  const date = new Date(value);
  date.setUTCHours(0, 0, 0, 0);
  return date;
}

function getOverlapRange(
  startDate: Date,
  endDate: Date,
  periodStart?: Date,
  periodEnd?: Date,
): { start: Date; end: Date } | null {
  const startBoundary = clampToDay(periodStart ?? startDate);
  const endBoundary = clampToDay(periodEnd ?? endDate);
  const overlapStart = clampToDay(new Date(Math.max(startDate.getTime(), startBoundary.getTime())));
  const overlapEnd = clampToDay(new Date(Math.min(endDate.getTime(), endBoundary.getTime())));

  if (overlapStart > overlapEnd) {
    return null;
  }

  return { start: overlapStart, end: overlapEnd };
}

export function countCalendarDaysInPeriod(
  vacation: VacationSpan,
  periodStart?: Date,
  periodEnd?: Date,
): number {
  const overlap = getOverlapRange(vacation.startDate, vacation.endDate, periodStart, periodEnd);
  if (!overlap) {
    return 0;
  }

  if (vacation.isHalfDay) {
    return 0.5;
  }

  const ms = overlap.end.getTime() - overlap.start.getTime();
  return Math.round(ms / 86_400_000) + 1;
}

export function countVacationChargeableDays(
  input: CountVacationChargeableDaysInput,
): number {
  const overlap = getOverlapRange(
    input.vacation.startDate,
    input.vacation.endDate,
    input.periodStart,
    input.periodEnd,
  );
  if (!overlap) {
    return 0;
  }

  const holidaySet = new Set(
    input.calendarHolidayStrings
      ? input.calendarHolidayStrings.filter((isoDate) => isoDate >= toIsoDate(overlap.start) && isoDate <= toIsoDate(overlap.end))
      : getCalendarHolidayStrings(
          overlap.start,
          overlap.end,
          input.countryCode,
          input.federalState,
          input.metroCityName,
        ),
  );

  for (const isoDate of input.publicHolidayStrings ?? []) {
    if (isoDate >= toIsoDate(overlap.start) && isoDate <= toIsoDate(overlap.end)) {
      holidaySet.add(isoDate);
    }
  }

  if (input.vacation.isHalfDay) {
    return holidaySet.has(toIsoDate(overlap.start)) ? 0 : 0.5;
  }

  let total = 0;
  const cursor = new Date(overlap.start);

  while (cursor <= overlap.end) {
    if (!holidaySet.has(toIsoDate(cursor))) {
      total += 1;
    }

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

  return total;
}
