import type { WeekdayAvailability } from "@capakraken/shared"; import type { TimelineAssignmentEntry } from "./TimelineContext.js"; const DAY_KEYS: (keyof WeekdayAvailability)[] = [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", ]; export const DEFAULT_TIMELINE_AVAILABILITY: WeekdayAvailability = { sunday: 0, monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, saturday: 0, }; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } export function toLocalStartOfDay(value: Date | string): Date { const date = value instanceof Date ? new Date(value) : new Date(value); date.setHours(0, 0, 0, 0); return date; } export function toLocalDateKey(value: Date | string): string { const date = toLocalStartOfDay(value); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; } export function normalizeTimelineAvailability(value: unknown): WeekdayAvailability { if (!isRecord(value)) { return DEFAULT_TIMELINE_AVAILABILITY; } const normalized = { ...DEFAULT_TIMELINE_AVAILABILITY }; for (const key of DAY_KEYS) { const next = value[key]; if (typeof next === "number" && Number.isFinite(next)) { normalized[key] = next; } } return normalized; } export function getTimelineAvailabilityHoursForDate( availability: WeekdayAvailability, date: Date, ): number { const dayKey = DAY_KEYS[toLocalStartOfDay(date).getDay()]; return dayKey ? (availability[dayKey] ?? 0) : 0; } export function getEffectiveAllocationAvailability( allocation: Pick, ): WeekdayAvailability { const availability = normalizeTimelineAvailability(allocation.resource?.availability); const metadata = allocation.metadata as Record | null; const includeSaturday = metadata?.includeSaturday === true; return includeSaturday ? availability : { ...availability, saturday: 0 }; } export function isAllocationScheduledOnDate( allocation: Pick, date: Date, ): boolean { const target = toLocalStartOfDay(date); const start = toLocalStartOfDay(allocation.startDate); const end = toLocalStartOfDay(allocation.endDate); if (target < start || target > end) { return false; } return getTimelineAvailabilityHoursForDate(getEffectiveAllocationAvailability(allocation), target) > 0; } export function buildAllocationWorkingDaySegments( allocation: Pick, rangeStart?: Date, rangeEnd?: Date, ): Array<{ start: Date; end: Date }> { const availability = getEffectiveAllocationAvailability(allocation); const start = toLocalStartOfDay(rangeStart ?? allocation.startDate); const end = toLocalStartOfDay(rangeEnd ?? allocation.endDate); if (start > end) { return []; } const segments: Array<{ start: Date; end: Date }> = []; let segmentStart: Date | null = null; let segmentEnd: Date | null = null; const cursor = new Date(start); while (cursor <= end) { const isWorkingDay = getTimelineAvailabilityHoursForDate(availability, cursor) > 0; if (isWorkingDay) { if (!segmentStart) { segmentStart = new Date(cursor); } segmentEnd = new Date(cursor); } else if (segmentStart && segmentEnd) { segments.push({ start: segmentStart, end: segmentEnd }); segmentStart = null; segmentEnd = null; } cursor.setDate(cursor.getDate() + 1); } if (segmentStart && segmentEnd) { segments.push({ start: segmentStart, end: segmentEnd }); } return segments; }