<?php

namespace App\Libraries;

/**
 * One physical overnight bus run — multiple pickup points, one seat map, two calendar meanings:
 *
 * - Service date (bus run): calendar day the bus departs its origin (A @ 10 PM on 16 Aug → 16 Aug).
 * - Boarding date: calendar day at the passenger's own pickup (B @ 4 AM → 17 Aug).
 *
 * Search / ticket list / passenger journey list use BOARDING date.
 * Inventory keys and journeylists storage use SERVICE date.
 *
 * Seat policy: segment overlap on the main trip stoppage (handoff at drop=pick allowed).
 * Short-hop resale after drop-off is disabled by default — see allowsShortSegmentReselling().
 */
class OvernightTripRunSupport
{
    protected TripJourneyDateHelper $helper;

    public function __construct(?TripJourneyDateHelper $helper = null)
    {
        $this->helper = $helper ?? new TripJourneyDateHelper();
    }

    /**
     * Resolve one boarding context from user search date + pickup stand.
     *
     * @return array{
     *   service_date: string,
     *   boarding_date: string,
     *   boarding_datetime: string,
     *   pick_stand_id: int,
     *   inventory_dates: list<string>
     * }
     */
    public function resolveBoardingSearch(
        int $tripId,
        int $subtripId,
        string $boardingSearchDate,
        int $pickStandId = 0
    ): array {
        BookingInventorySupport::syncBookingTimezone();
        $boardingSearchDate = BookingInventorySupport::normalizeCalendarDate($boardingSearchDate);

        if ($tripId <= 0 || $subtripId <= 0) {
            return [
                'service_date' => $boardingSearchDate,
                'boarding_date' => $boardingSearchDate,
                'boarding_datetime' => $boardingSearchDate . ' 00:00:00',
                'pick_stand_id' => $pickStandId,
                'inventory_dates' => [$boardingSearchDate],
            ];
        }

        if ($pickStandId <= 0) {
            $eligible = $this->helper->pickStandIdsBoardingOnSearchDate($tripId, $subtripId, $boardingSearchDate);
            $pickStandId = $eligible[0] ?? BookingInventorySupport::resolveDefaultPickStandId($tripId, $subtripId);
        }

        $serviceDate = $this->helper->resolveCanonicalServiceDateForBoardingSearch(
            $tripId,
            $subtripId,
            $boardingSearchDate
        ) ?? $this->helper->boardingDayToBusRunDate(
            $tripId,
            $subtripId,
            $boardingSearchDate,
            $pickStandId
        );
        $boardingDate = $this->helper->getBoardingDateFromBusStartDate(
            $tripId,
            $subtripId,
            $serviceDate,
            $pickStandId
        );

        return [
            'service_date' => $serviceDate,
            'boarding_date' => $boardingDate,
            'boarding_datetime' => $this->helper->getBoardingDateTime($tripId, $subtripId, $serviceDate, $pickStandId),
            'pick_stand_id' => $pickStandId,
            'inventory_dates' => BookingInventorySupport::buildInventoryMatchDates($serviceDate, $boardingDate),
        ];
    }

    /**
     * Whether this subtrip should appear when the user searches $boardingSearchDate.
     */
    public function subtripVisibleOnBoardingSearchDate(int $tripId, int $subtripId, string $boardingSearchDate): bool
    {
        return $this->helper->pickStandIdsBoardingOnSearchDate($tripId, $subtripId, $boardingSearchDate) !== [];
    }

    /**
     * @return list<int>
     */
    public function pickStandsForBoardingSearchDate(int $tripId, int $subtripId, string $boardingSearchDate): array
    {
        return $this->helper->pickStandIdsBoardingOnSearchDate($tripId, $subtripId, $boardingSearchDate);
    }

    /**
     * Admin trip finder: search date is bus run / manifest day (main trip start date).
     *
     * @return list<int>
     */
    public function pickStandsForServiceSearchDate(int $tripId, int $subtripId, string $serviceSearchDate): array
    {
        return $this->helper->pickStandIdsForServiceSearchDate($tripId, $subtripId, $serviceSearchDate);
    }

    /**
     * All pickup stand ids (pickdrops.id) configured on a subtrip — admin booking shows every pickup.
     *
     * @return list<int>
     */
    public function allPickupStandIdsForSubtrip(int $tripId, int $subtripId): array
    {
        return $this->helper->listSegmentPickupStandIds($tripId, $subtripId);
    }

    /**
     * Pickup stand ids for passenger boarding-point UI (app / website).
     *
     * Overnight / cross-midnight: every configured pickup on the segment (same as admin).
     * Same-day routes: only stands that board on the search calendar day.
     *
     * @return list<int>
     */
    public function pickStandsForPickupPicker(int $tripId, int $subtripId, string $searchDate): array
    {
        if ($this->requiresExplicitPickStand($tripId, $subtripId)) {
            return $this->allPickupStandIdsForSubtrip($tripId, $subtripId);
        }

        return $this->pickStandsForBoardingSearchDate($tripId, $subtripId, $searchDate);
    }

    /**
     * Keep only pickup stands that board on the search date; drop stands unchanged.
     *
     * @param list<object> $pickdrops
     * @return list<object>
     */
    public function filterPickdropsForBoardingSearch(
        int $tripId,
        int $subtripId,
        string $boardingSearchDate,
        array $pickdrops
    ): array {
        $eligible = array_flip($this->pickStandsForBoardingSearchDate($tripId, $subtripId, $boardingSearchDate));
        if ($eligible === []) {
            return $pickdrops;
        }

        $filtered = [];
        foreach ($pickdrops as $row) {
            $type = (int) ($row->pickdrop_type ?? $row->type ?? 0);
            $id   = (int) ($row->pickdropid ?? $row->id ?? 0);
            // Pickups: only stands that board on the search (boarding) date. Drops: always keep.
            if ($type === 1 && $id > 0 && ! isset($eligible[$id])) {
                continue;
            }
            $filtered[] = $row;
        }

        return $filtered;
    }

    /**
     * Passenger-facing list row (search date = boarding day at pickup).
     */
    public function passengerListMatchesSearchDate(
        int $tripId,
        int $subtripId,
        int $pickStandId,
        string $boardingSearchDate,
        ?string $ticketJourneyData = null,
        ?string $manifestServiceDate = null
    ): bool {
        return $this->helper->rowMatchesPassengerBoardingListSearch(
            $tripId,
            $subtripId,
            $pickStandId,
            $boardingSearchDate,
            $ticketJourneyData,
            $manifestServiceDate
        );
    }

    /**
     * Driver manifest: all passengers on one physical run (service date at origin).
     */
    public function driverManifestMatchesSearchDate(
        int $tripId,
        int $subtripId,
        string $storedManifestDate,
        int $pickStandId,
        string $serviceSearchDate,
        ?string $ticketJourneyData = null
    ): bool {
        return $this->helper->rowMatchesJourneyListManifestSearch(
            $tripId,
            $subtripId,
            $storedManifestDate,
            $pickStandId,
            $serviceSearchDate,
            $ticketJourneyData
        );
    }

    /**
     * Short A→B then resell B→F on same seat: disabled — any overlapping segment blocks the seat.
     */
    public static function allowsShortSegmentReselling(): bool
    {
        return false;
    }

    /**
     * True when this subtrip has pickups that board on more than one calendar day for the same bus run
     * (cross-midnight / overnight).
     */
    public function requiresExplicitPickStand(int $tripId, int $subtripId): bool
    {
        if ($tripId <= 0 || $subtripId <= 0) {
            return false;
        }

        return $this->helper->tripSpansMultipleBoardingCalendarDays($tripId, $subtripId);
    }

    /**
     * Client triplist / seat APIs: passenger must choose a boarding point (pickdrop id) before inventory loads.
     * True for every bookable subtrip with at least one pickup — not only cross-midnight runs.
     */
    public function clientMustSelectPickstand(int $tripId, int $subtripId): bool
    {
        if ($tripId <= 0 || $subtripId <= 0) {
            return false;
        }

        return $this->allPickupStandIdsForSubtrip($tripId, $subtripId) !== [];
    }

    /**
     * @return string|null Error message when pickstand is required but missing.
     */
    public function validatePickStandForSeatInventory(int $tripId, int $subtripId, int $pickStandId): ?string
    {
        if ($pickStandId > 0) {
            return null;
        }

        if (! $this->clientMustSelectPickstand($tripId, $subtripId)) {
            return null;
        }

        return 'pickstand is required before checking seat availability.';
    }
}
