import { Logger } from '@nestjs/common';

const logger = new Logger('ScheduleTimesUtils');

export type StopForTiming = {
  sequence: number;
  distanceKm: number | null;
  travelMinutesToStop: number | null;
  checkinMinutesBefore: number | null;
};

export type PlannedStopTime = {
  sequence: number;
  plannedArrivalAt: string | undefined;
  plannedDepartureAt: string | undefined;
};

/**
 * Computes each stop's planned arrival/departure time by walking the route in sequence order.
 *
 * Model per intermediate stop:
 *   arrival  = departureCursor + travelMinutesToStop  (falls back to distance interpolation)
 *   departure = arrival + checkinMinutesBefore          (dwell time; 0 if null)
 *   next-stop travel starts from this departure, not from arrival.
 *
 * This means booking for stop B closes at B.departureAt − checkinMinutesBefore = B.arrivalAt,
 * i.e. the train must not yet have arrived at the stop for a booking to succeed.
 *
 * The last stop is always locked to arr so schedule.arrivalAt stays authoritative.
 */
export function computePlannedStopTimes(
  route: { id: string; stops: StopForTiming[] },
  dep: Date,
  arr: Date,
): PlannedStopTime[] {
  const totalDuration = arr.getTime() - dep.getTime();
  const totalDistance = route.stops[route.stops.length - 1].distanceKm || 0;

  // cursor tracks the DEPARTURE time from the most-recently processed stop.
  let departureCursor = dep;

  return route.stops.map((stop, index) => {
    if (index === 0) {
      // Origin: train starts here, no arrival.
      departureCursor = dep;
      return { sequence: stop.sequence, plannedArrivalAt: undefined, plannedDepartureAt: dep.toISOString() };
    }

    if (index === route.stops.length - 1) {
      // Final destination: arrival is authoritative; no departure.
      return { sequence: stop.sequence, plannedArrivalAt: arr.toISOString(), plannedDepartureAt: undefined };
    }

    // Intermediate stop: compute arrival from the previous stop's departure.
    let arrivalAt: Date;
    if (stop.travelMinutesToStop != null) {
      arrivalAt = new Date(departureCursor.getTime() + stop.travelMinutesToStop * 60_000);
    } else {
      const stopDistance = stop.distanceKm || 0;
      const progress = totalDistance > 0 ? stopDistance / totalDistance : index / (route.stops.length - 1);
      arrivalAt = new Date(dep.getTime() + totalDuration * progress);
      logger.warn(`Route ${route.id} stop seq ${stop.sequence} missing travelMinutesToStop; falling back to distance interpolation`);
    }

    // Dwell at this stop = checkinMinutesBefore (the boarding window).
    const dwell = stop.checkinMinutesBefore ?? 0;
    const departureAt = new Date(arrivalAt.getTime() + dwell * 60_000);
    departureCursor = departureAt;

    return {
      sequence: stop.sequence,
      plannedArrivalAt: arrivalAt.toISOString(),
      plannedDepartureAt: departureAt.toISOString(),
    };
  });
}
