import { type WeekdayAvailability } from "@capakraken/shared";
import { type ResourceDailyAvailabilityContext } from "../lib/resource-capacity.js";

const DAY_KEYS: (keyof WeekdayAvailability)[] = [
  "sunday",
  "monday",
  "tuesday",
  "wednesday",
  "thursday",
  "friday",
  "saturday",
];

export const ACTIVE_STATUSES = new Set(["PROPOSED", "CONFIRMED", "ACTIVE"]);

export function toIsoDate(value: Date): string {
  return value.toISOString().slice(0, 10);
}

function createUtcDate(year: number, monthIndex: number, day: number): Date {
  return new Date(Date.UTC(year, monthIndex, day));
}

function normalizeUtcDate(value: Date): Date {
  return createUtcDate(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate());
}

export function createDateRange(input: {
  startDate?: Date | undefined;
  endDate?: Date | undefined;
  durationDays?: number | undefined;
}): { startDate: Date; endDate: Date } {
  const startDate = input.startDate
    ? normalizeUtcDate(input.startDate)
    : createUtcDate(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate());
  const endDate = input.endDate
    ? normalizeUtcDate(input.endDate)
    : createUtcDate(
        startDate.getUTCFullYear(),
        startDate.getUTCMonth(),
        startDate.getUTCDate() + Math.max((input.durationDays ?? 21) - 1, 0),
      );

  if (endDate < startDate) {
    throw new Error("endDate must be on or after startDate.");
  }

  return { startDate, endDate };
}

export function round1(value: number): number {
  return Math.round(value * 10) / 10;
}

export function getBaseDayAvailability(
  availability: WeekdayAvailability,
  date: Date,
): number {
  const key = DAY_KEYS[date.getUTCDay()];
  return key ? (availability[key] ?? 0) : 0;
}

export function getEffectiveDayAvailability(
  availability: WeekdayAvailability,
  date: Date,
  context: ResourceDailyAvailabilityContext | undefined,
): number {
  const key = DAY_KEYS[date.getUTCDay()];
  const baseHours = key ? (availability[key] ?? 0) : 0;
  if (baseHours <= 0) {
    return 0;
  }
  const fraction = context?.absenceFractionsByDate.get(toIsoDate(date)) ?? 0;
  return Math.max(0, baseHours * (1 - fraction));
}

function overlapsDateRange(startDate: Date, endDate: Date, date: Date): boolean {
  return date >= startDate && date <= endDate;
}

export function averagePerWorkingDay(totalHours: number, workingDays: number): number {
  if (workingDays <= 0) {
    return 0;
  }
  return round1(totalHours / workingDays);
}

export function createLocationLabel(input: {
  countryCode?: string | null;
  federalState?: string | null;
  metroCityName?: string | null;
}): string {
  return [
    input.countryCode ?? null,
    input.federalState ?? null,
    input.metroCityName ?? null,
  ].filter((value): value is string => Boolean(value && value.trim().length > 0)).join(" / ");
}

export function calculateAllocatedHoursForDay(input: {
  bookings: Array<{ startDate: Date; endDate: Date; hoursPerDay: number; status: string; isChargeable?: boolean }>;
  date: Date;
  context: ResourceDailyAvailabilityContext | undefined;
}): { allocatedHours: number; chargeableHours: number } {
  const isoDate = toIsoDate(input.date);
  const dayFraction = Math.max(0, 1 - (input.context?.absenceFractionsByDate.get(isoDate) ?? 0));

  return input.bookings.reduce(
    (acc, booking) => {
      if (!ACTIVE_STATUSES.has(booking.status) || !overlapsDateRange(booking.startDate, booking.endDate, input.date)) {
        return acc;
      }

      const effectiveHours = booking.hoursPerDay * dayFraction;
      acc.allocatedHours += effectiveHours;
      if (booking.isChargeable) {
        acc.chargeableHours += effectiveHours;
      }
      return acc;
    },
    { allocatedHours: 0, chargeableHours: 0 },
  );
}
