import type { TimelineAssignmentEntry } from "./TimelineContext.js";
import type { HeatmapHoverData } from "./TimelineTooltip.js";
import type { ResourceCapacitySeries } from "./timelineCapacity.js";
import { toLocalDateKey } from "./timelineAvailability.js";

type HeatmapAccumulator = {
  shortCode: string;
  projectName: string;
  orderType: string;
  hours: number;
  responsiblePerson?: string | null;
  role?: string | null;
  status?: string;
  startDate?: string;
  endDate?: string;
};

export function buildResourceHeatmapHover(
  date: Date,
  allocations: TimelineAssignmentEntry[],
  options?: {
    capacityHours?: number;
    bookingFactor?: number;
  },
): HeatmapHoverData {
  const target = new Date(date);
  target.setHours(0, 0, 0, 0);
  const targetTime = target.getTime();
  const bookingFactor = options?.bookingFactor ?? 1;
  const capacityHours = options?.capacityHours ?? 8;

  const projectHours = new Map<string, HeatmapAccumulator>();

  for (const allocation of allocations) {
    const start = new Date(allocation.startDate);
    start.setHours(0, 0, 0, 0);
    const end = new Date(allocation.endDate);
    end.setHours(0, 0, 0, 0);
    if (targetTime < start.getTime() || targetTime > end.getTime()) continue;

    const existing = projectHours.get(allocation.projectId);
    if (existing) {
      existing.hours += allocation.hoursPerDay * bookingFactor;
      continue;
    }

    projectHours.set(allocation.projectId, {
      shortCode: allocation.project.shortCode,
      projectName: allocation.project.name,
      orderType: allocation.project.orderType,
      hours: allocation.hoursPerDay * bookingFactor,
      responsiblePerson:
        (allocation.project as { responsiblePerson?: string | null }).responsiblePerson ?? null,
      role: allocation.role ?? allocation.roleEntity?.name ?? null,
      status: allocation.status,
      startDate: toLocalDateKey(allocation.startDate),
      endDate: toLocalDateKey(allocation.endDate),
    });
  }

  const breakdown: HeatmapHoverData["breakdown"] = [...projectHours.entries()]
    .map(([projectId, value]) => ({
      projectId,
      shortCode: value.shortCode,
      projectName: value.projectName,
      orderType: value.orderType,
      hoursPerDay: value.hours,
      ...(value.responsiblePerson !== undefined ? { responsiblePerson: value.responsiblePerson } : {}),
      ...(value.role !== undefined ? { role: value.role } : {}),
      ...(value.status !== undefined ? { status: value.status } : {}),
      ...(value.startDate !== undefined ? { startDate: value.startDate } : {}),
      ...(value.endDate !== undefined ? { endDate: value.endDate } : {}),
    }))
    .sort((a, b) => b.hoursPerDay - a.hoursPerDay);

  const totalH = breakdown.reduce((sum, entry) => sum + entry.hoursPerDay, 0);

  return {
    date,
    totalH,
    pct: capacityHours > 0 ? (totalH / capacityHours) * 100 : 0,
    breakdown,
  };
}

export function buildResourceHeatmapSeries(
  allocsByResource: Map<string, TimelineAssignmentEntry[]>,
  dates: Date[],
  resourceCapacityById?: Map<string, ResourceCapacitySeries>,
): {
  resourceHeatmapById: Map<string, (HeatmapHoverData | null)[]>;
  resourceTotalHoursById: Map<string, number[]>;
} {
  const dateIndexByTime = new Map<number, number>();
  dates.forEach((date, index) => {
    const normalized = new Date(date);
    normalized.setHours(0, 0, 0, 0);
    dateIndexByTime.set(normalized.getTime(), index);
  });

  const resourceHeatmapById = new Map<string, (HeatmapHoverData | null)[]>();
  const resourceTotalHoursById = new Map<string, number[]>();

  for (const [resourceId, allocs] of allocsByResource) {
    if (allocs.length === 0) continue;
    const capacity = resourceCapacityById?.get(resourceId);

    const totalHours = new Array<number>(dates.length).fill(0);
    const breakdownMaps = Array.from(
      { length: dates.length },
      () => new Map<string, HeatmapAccumulator>(),
    );

    for (const alloc of allocs) {
      const current = new Date(alloc.startDate);
      current.setHours(0, 0, 0, 0);
      const end = new Date(alloc.endDate);
      end.setHours(0, 0, 0, 0);

      while (current.getTime() <= end.getTime()) {
        const dayIndex = dateIndexByTime.get(current.getTime());
        if (dayIndex !== undefined) {
          const effectiveHours =
            alloc.hoursPerDay * (capacity?.bookingFactorsByDay[dayIndex] ?? 1);
          totalHours[dayIndex] = (totalHours[dayIndex] ?? 0) + effectiveHours;

          const dayBreakdown = breakdownMaps[dayIndex];
          if (dayBreakdown) {
            const existing = dayBreakdown.get(alloc.projectId);
            if (existing) {
              existing.hours += effectiveHours;
            } else {
              dayBreakdown.set(alloc.projectId, {
                shortCode: alloc.project.shortCode,
                projectName: alloc.project.name,
                orderType: alloc.project.orderType,
                responsiblePerson:
                  (alloc.project as { responsiblePerson?: string | null }).responsiblePerson ??
                  null,
                hours: effectiveHours,
              });
            }
          }
        }

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

    resourceTotalHoursById.set(resourceId, totalHours);
    resourceHeatmapById.set(
      resourceId,
      totalHours.map((totalH, dayIndex) => {
        if (totalH === 0) return null;

        const dayBreakdown = breakdownMaps[dayIndex];
        if (!dayBreakdown) return null;

        const breakdown = [...dayBreakdown.entries()]
          .map(([projectId, value]) => ({
            projectId,
            shortCode: value.shortCode,
            projectName: value.projectName,
            orderType: value.orderType,
            responsiblePerson: value.responsiblePerson ?? null,
            hoursPerDay: value.hours,
          }))
          .sort((a, b) => b.hoursPerDay - a.hoursPerDay);

        return {
          date: dates[dayIndex] ?? new Date(),
          totalH,
          pct:
            (capacity?.capacityHoursByDay[dayIndex] ?? 8) > 0
              ? (totalH / (capacity?.capacityHoursByDay[dayIndex] ?? 8)) * 100
              : 0,
          breakdown,
        };
      }),
    );
  }

  return {
    resourceHeatmapById,
    resourceTotalHoursById,
  };
}
