import type { WeekdayAvailability } from "@capakraken/shared";
import type { TimelineAssignmentEntry, VacationEntry } from "./TimelineContext.js";
import {
  DEFAULT_TIMELINE_AVAILABILITY,
  getTimelineAvailabilityHoursForDate,
  normalizeTimelineAvailability,
  toLocalDateKey,
  toLocalStartOfDay,
} from "./timelineAvailability.js";

const DEFAULT_AVAILABILITY: WeekdayAvailability = DEFAULT_TIMELINE_AVAILABILITY;

export type ResourceCapacitySeries = {
  baseHoursByDay: number[];
  capacityHoursByDay: number[];
  bookingFactorsByDay: number[];
};

function normalizeAvailability(value: unknown): WeekdayAvailability {
  return normalizeTimelineAvailability(value);
}

function getAvailabilityHoursForDate(availability: WeekdayAvailability, date: Date): number {
  return getTimelineAvailabilityHoursForDate(availability, date);
}

function resolveResourceAvailability(allocs: TimelineAssignmentEntry[]): WeekdayAvailability {
  for (const alloc of allocs) {
    const availability = alloc.resource?.availability;
    if (availability !== null && availability !== undefined) {
      return normalizeAvailability(availability);
    }
  }
  return DEFAULT_AVAILABILITY;
}

function buildAbsenceFractionsByDate(
  vacations: VacationEntry[],
  periodStart: Date,
  periodEnd: Date,
): Map<string, number> {
  const holidayDates = new Set<string>();
  const vacationFractionsByDate = new Map<string, number>();
  const absenceFractionsByDate = new Map<string, number>();
  const normalizedStart = toLocalStartOfDay(periodStart);
  const normalizedEnd = toLocalStartOfDay(periodEnd);

  for (const vacation of vacations) {
    const isPublicHoliday = vacation.type === "PUBLIC_HOLIDAY";
    const isApprovedVacation = vacation.status === "APPROVED";

    if (!isPublicHoliday && !isApprovedVacation) {
      continue;
    }

    const overlapStart = new Date(
      Math.max(
        toLocalStartOfDay(vacation.startDate).getTime(),
        normalizedStart.getTime(),
      ),
    );
    const overlapEnd = new Date(
      Math.min(
        toLocalStartOfDay(vacation.endDate).getTime(),
        normalizedEnd.getTime(),
      ),
    );

    if (overlapStart > overlapEnd) {
      continue;
    }

    const fraction = vacation.isHalfDay ? 0.5 : 1;
    const cursor = new Date(overlapStart);

    while (cursor <= overlapEnd) {
      const isoDate = toLocalDateKey(cursor);

      if (isPublicHoliday) {
        holidayDates.add(isoDate);
      } else {
        const existingVacation = vacationFractionsByDate.get(isoDate) ?? 0;
        vacationFractionsByDate.set(isoDate, Math.max(existingVacation, fraction));
      }

      const existingAbsence = absenceFractionsByDate.get(isoDate) ?? 0;
      if (isPublicHoliday || !holidayDates.has(isoDate)) {
        absenceFractionsByDate.set(isoDate, Math.max(existingAbsence, fraction));
      }

      cursor.setDate(cursor.getDate() + 1);
    }
  }

  for (const isoDate of holidayDates) {
    const existingAbsence = absenceFractionsByDate.get(isoDate) ?? 0;
    absenceFractionsByDate.set(isoDate, Math.max(existingAbsence, 1));
  }

  return absenceFractionsByDate;
}

export function buildResourceCapacitySeries(
  allocsByResource: Map<string, TimelineAssignmentEntry[]>,
  vacationsByResource: Map<string, VacationEntry[]>,
  dates: Date[],
): Map<string, ResourceCapacitySeries> {
  const result = new Map<string, ResourceCapacitySeries>();

  if (dates.length === 0) {
    return result;
  }

  const periodStart = dates[0]!;
  const periodEnd = dates[dates.length - 1]!;

  for (const [resourceId, allocs] of allocsByResource) {
    const availability = resolveResourceAvailability(allocs);
    const absenceFractionsByDate = buildAbsenceFractionsByDate(
      vacationsByResource.get(resourceId) ?? [],
      periodStart,
      periodEnd,
    );

    const baseHoursByDay: number[] = [];
    const capacityHoursByDay: number[] = [];
    const bookingFactorsByDay: number[] = [];

    for (const date of dates) {
      const baseHours = getAvailabilityHoursForDate(availability, date);
      const absenceFraction = absenceFractionsByDate.get(toLocalDateKey(date)) ?? 0;
      const bookingFactor = baseHours > 0 ? Math.max(0, 1 - absenceFraction) : 0;

      baseHoursByDay.push(baseHours);
      bookingFactorsByDay.push(bookingFactor);
      capacityHoursByDay.push(baseHours * bookingFactor);
    }

    result.set(resourceId, {
      baseHoursByDay,
      capacityHoursByDay,
      bookingFactorsByDay,
    });
  }

  return result;
}
