<?php

namespace App\Libraries;

use App\Libraries\EgyptTime;
use Modules\Schedule\Models\ScheduleModel;
use Modules\Trip\Models\TripModel;
use Modules\Trip\Models\SubtripModel;
use Modules\Trip\Models\PickdropModel;

/**
 * Journey calendar dates — three meanings (do not mix APIs):
 *
 * 1. Bus run / manifest day — when the bus departs (driver printout, seat inventory key).
 * 2. Boarding day — passenger pickup calendar day (tickets.journeydata, ticket list search).
 * 3. User-selected date — what the UI sends; may be (1) or (2) depending on screen.
 *
 * Which method to use:
 * - Trip search / seat map / checkSeats after search: resolveBusRunDateForTripSearch() — literal date, no shift.
 * - Booking, payment, journeylists, hold save: bus run day from trip search (normalize only).
 * - tickets.journeydata → bus run: boardingDayToBusRunDate() for inventory filters.
 * - tickets.journeydata on insert: getTicketJourneyDateForStorage(busRun, pick) → boarding day.
 *
 * pickdrops.trip_id stores subtrips.id (NOT trips.id); main-route stands use trips.id in some legacy rows.
 */
class TripJourneyDateHelper
{
    protected $tripModel;
    protected $subtripModel;
    protected $pickdropModel;
    protected $scheduleModel;

    /** @var array<int, object|null> */
    protected $tripCache = [];

    /** @var array<int, object|null> */
    protected $scheduleCache = [];

    /** @var array<int, array> */
    protected $mainPickdropsCache = [];

    /** @var array<int, array> */
    protected $subtripPickupsCache = [];

    /** @var array<int, object|null> */
    protected $pickdropByIdCache = [];

    /** @var array<string, string> */
    protected $boardingDateCache = [];

    /** @var array<string, string> */
    protected $busStartDateCache = [];

    public function __construct()
    {
        $this->tripModel      = new TripModel();
        $this->subtripModel   = new SubtripModel();
        $this->pickdropModel  = new PickdropModel();
        $this->scheduleModel  = new ScheduleModel();
    }

    /**
     * Trip finder / seat map: user-selected calendar date is already the bus run day.
     */
    public function resolveBusRunDateForTripSearch(string $userSelectedDate): string
    {
        return EgyptTime::toDateString($userSelectedDate);
    }

    /**
     * Bus manifest day when saving a booking — trip search already passes the bus run day.
     */
    public function getManifestDateForJourneylist(
        int $mainTripId,
        int $subtripId,
        string $selectedCalendarDate,
        int $pickStandId
    ): string {
        unset($mainTripId, $subtripId, $pickStandId);

        return EgyptTime::toDateString($selectedCalendarDate);
    }

    /**
     * Convert tickets.journeydata (boarding calendar day) to bus run day for inventory matching.
     */
    public function boardingDayToBusRunDate(
        int $mainTripId,
        int $subtripId,
        string $boardingCalendarDate,
        int $pickStandId
    ): string {
        $boarding = EgyptTime::toDateString($boardingCalendarDate);

        if ($mainTripId <= 0 || $boarding === '1970-01-01') {
            return $boarding;
        }

        $mainTrip = $this->getTrip($mainTripId);
        if (! $mainTrip) {
            return $boarding;
        }

        $pickupDayOffset = $this->getPickupDayOffset($mainTrip, $mainTripId, $subtripId, $pickStandId);
        if ($pickupDayOffset === null || $pickupDayOffset < 1) {
            return $boarding;
        }

        return EgyptTime::toDateString($boarding . " -{$pickupDayOffset} day");
    }

    /**
     * @deprecated Use getManifestDateForJourneylist (booking) or resolveBusRunDateForTripSearch (search).
     */
    public function getBusStartDateForJourneylist(int $mainTripId, int $subtripId, string $userJourneyDate, int $pickStandId): string
    {
        $userJourneyDate = EgyptTime::toDateString($userJourneyDate);
        $cacheKey        = "{$mainTripId}|{$subtripId}|{$userJourneyDate}|{$pickStandId}|bus";

        if (isset($this->busStartDateCache[$cacheKey])) {
            return $this->busStartDateCache[$cacheKey];
        }

        $mainTrip = $this->getTrip($mainTripId);

        if (!$mainTrip) {
            return $this->busStartDateCache[$cacheKey] = $userJourneyDate;
        }

        $pickupDayOffset = $this->getPickupDayOffset($mainTrip, $mainTripId, $subtripId, $pickStandId);

        if ($pickupDayOffset === null) {
            return $this->busStartDateCache[$cacheKey] = $userJourneyDate;
        }

        return $this->busStartDateCache[$cacheKey] = date('Y-m-d', strtotime($userJourneyDate . " -{$pickupDayOffset} day"));
    }

    /**
     * Compute the passenger's actual boarding date from the normalized bus start date.
     * Boarding Date = Bus Start Date + offset of pickup stand.
     */
    public function getBoardingDateFromBusStartDate(int $mainTripId, int $subtripId, string $busStartDate, int $pickStandId): string
    {
        $busStartDate = EgyptTime::toDateString($busStartDate);
        $cacheKey     = "{$mainTripId}|{$subtripId}|{$busStartDate}|{$pickStandId}|board";

        if (isset($this->boardingDateCache[$cacheKey])) {
            return $this->boardingDateCache[$cacheKey];
        }

        $mainTrip = $this->getTrip($mainTripId);

        if (!$mainTrip) {
            return $this->boardingDateCache[$cacheKey] = $busStartDate;
        }

        $pickupDayOffset = $this->getPickupDayOffset($mainTrip, $mainTripId, $subtripId, $pickStandId);

        if ($pickupDayOffset === null) {
            return $this->boardingDateCache[$cacheKey] = $busStartDate;
        }

        return $this->boardingDateCache[$cacheKey] = date('Y-m-d', strtotime($busStartDate . " +{$pickupDayOffset} day"));
    }

    /**
     * Passenger boarding day for tickets.journeydata (ticket list / invoices).
     *
     * @param string $busStartDate Normalized bus run day from getManifestDateForJourneylist()
     */
    public function getTicketJourneyDateForStorage(
        int $mainTripId,
        int $subtripId,
        string $busStartDate,
        int $pickStandId
    ): string {
        $busStartDate = EgyptTime::toDateString($busStartDate);

        if ($mainTripId <= 0 || $busStartDate === '1970-01-01') {
            return $busStartDate;
        }

        return $this->getBoardingDateFromBusStartDate($mainTripId, $subtripId, $busStartDate, $pickStandId);
    }

    /**
     * Bus run day(s) from tickets.journeydata for inventory (handles legacy mis-storage).
     *
     * @return list<string>
     */
    public function resolveBusRunDaysFromTicketJourneyData(
        int $mainTripId,
        int $subtripId,
        string $ticketJourneyData,
        int $pickStandId
    ): array {
        $busRun = $this->resolveBusRunDayFromTicketJourneyData(
            $mainTripId,
            $subtripId,
            $ticketJourneyData,
            $pickStandId
        );

        return [$busRun];
    }

    /**
     * Bus run day from tickets.journeydata (boarding day, or legacy rows that stored bus run there).
     */
    public function resolveBusRunDayFromTicketJourneyData(
        int $mainTripId,
        int $subtripId,
        string $ticketJourneyData,
        int $pickStandId
    ): string {
        $ticketDay = EgyptTime::toDateString($ticketJourneyData);

        if ($mainTripId <= 0 || $ticketDay === '1970-01-01') {
            return $ticketDay;
        }

        $runIfBoarding = $this->boardingDayToBusRunDate($mainTripId, $subtripId, $ticketDay, $pickStandId);
        if ($this->getBoardingDateFromBusStartDate($mainTripId, $subtripId, $runIfBoarding, $pickStandId) === $ticketDay
            && $runIfBoarding !== $ticketDay) {
            return $runIfBoarding;
        }

        $boardingIfRun = $this->getBoardingDateFromBusStartDate(
            $mainTripId,
            $subtripId,
            $ticketDay,
            $pickStandId
        );
        if ($boardingIfRun !== $ticketDay
            && $this->boardingDayToBusRunDate($mainTripId, $subtripId, $boardingIfRun, $pickStandId) === $ticketDay) {
            return $ticketDay;
        }

        return $runIfBoarding;
    }

    /**
     * @deprecated Booking passes bus run day; use getManifestDateForJourneylist() or boardingDayToBusRunDate().
     */
    public function resolveServiceRunDateForInventory(
        int $mainTripId,
        int $subtripId,
        string $calendarDate,
        int $pickStandId
    ): string {
        return $this->getManifestDateForJourneylist($mainTripId, $subtripId, $calendarDate, $pickStandId);
    }

    /**
     * Journey date for ticket list, invoices, and API — normalized ticket/manifest day.
     */
    public function getPassengerBoardingDateForDisplay(
        int $mainTripId,
        int $subtripId,
        string $storedJourneyDate,
        int $pickStandId
    ): string {
        $stored = EgyptTime::toDateString($storedJourneyDate);
        if ($stored === '1970-01-01' || $mainTripId <= 0) {
            return $stored;
        }

        $boardingIfStoredIsRun = $this->getBoardingDateFromBusStartDate(
            $mainTripId,
            $subtripId,
            $stored,
            $pickStandId
        );

        if ($boardingIfStoredIsRun !== $stored) {
            $recomputedRun = $this->boardingDayToBusRunDate(
                $mainTripId,
                $subtripId,
                $boardingIfStoredIsRun,
                $pickStandId
            );
            if ($recomputedRun === $stored) {
                return $boardingIfStoredIsRun;
            }
        }

        return $stored;
    }

    /**
     * Calendar dates to match when searching the ticket list by journey date.
     *
     * @return list<string>
     */
    public function getTicketSearchMatchDates(string $searchDate): array
    {
        $searchDate = date('Y-m-d', strtotime($searchDate));

        return array_values(array_unique([
            $searchDate,
            date('Y-m-d', strtotime($searchDate . ' -1 day')),
            date('Y-m-d', strtotime($searchDate . ' +1 day')),
        ]));
    }

    /**
     * Pre-warm boarding-date cache for a set of ticket/journey rows (same request).
     *
     * @param iterable<object> $rows Objects with trip_id, subtrip_id, journeydata|journeydate, pick_stand_id
     */
    public function warmBoardingDateCache(iterable $rows): void
    {
        foreach ($rows as $row) {
            $this->getPassengerBoardingDateForDisplay(
                (int) ($row->trip_id ?? 0),
                (int) ($row->subtrip_id ?? 0),
                (string) ($row->journeydata ?? $row->journeydate ?? ''),
                (int) ($row->pick_stand_id ?? 0)
            );
        }
    }

    /**
     * Normalize time string to 24h format for comparison (e.g. "11:30PM" -> "23:30").
     */
    protected function normalizeTime(string $time): string
    {
        $time = trim($time);
        if (empty($time)) {
            return '00:00';
        }
        $timestamp = strtotime('1970-01-01 ' . $time);

        return $timestamp !== false ? date('H:i', $timestamp) : '00:00';
    }

    protected function getTrip(int $mainTripId): ?object
    {
        if ($mainTripId <= 0) {
            return null;
        }

        if (!array_key_exists($mainTripId, $this->tripCache)) {
            $this->tripCache[$mainTripId] = $this->tripModel->find($mainTripId) ?: null;
        }

        return $this->tripCache[$mainTripId];
    }

    protected function getSchedule(int $scheduleId): ?object
    {
        if ($scheduleId <= 0) {
            return null;
        }

        if (!array_key_exists($scheduleId, $this->scheduleCache)) {
            $this->scheduleCache[$scheduleId] = $this->scheduleModel->find($scheduleId) ?: null;
        }

        return $this->scheduleCache[$scheduleId];
    }

    /**
     * Return how many days after the bus start date this pickup happens.
     */
    protected function getPickupDayOffset(object $mainTrip, int $mainTripId, int $subtripId, int $pickStandId): ?int
    {
        $schedule          = $this->getSchedule((int) ($mainTrip->schedule_id ?? 0));
        $mainTripStartTime = $schedule->start_time ?? '00:00';

        $mainPickdrops = $this->getMainSubtripPickdrops($mainTripId);
        if (!empty($mainPickdrops)) {
            $offset = $this->getOffsetFromPickdropSequence($mainPickdrops, $mainTripStartTime, $pickStandId);
            if ($offset !== null) {
                return $offset;
            }

            $offset = $this->getOffsetFromMainRouteByPickdropStand($mainPickdrops, $mainTripStartTime, $pickStandId);
            if ($offset !== null) {
                return $offset;
            }
        }

        return $this->getOffsetFromActualPickup($subtripId, $pickStandId, $mainTripStartTime);
    }

    /**
     * Match a subtrip-only pickup stand to the main trip pickdrop list by stand_id.
     */
    protected function getOffsetFromMainRouteByPickdropStand(array $mainPickdrops, string $mainTripStartTime, int $pickStandId): ?int
    {
        $pickdrop = $this->getPickdropById($pickStandId);
        if (!$pickdrop) {
            return null;
        }

        $standId = (int) ($pickdrop->stand_id ?? 0);
        if ($standId <= 0) {
            return null;
        }

        foreach ($mainPickdrops as $item) {
            if ((int) ($item->stand_id ?? 0) !== $standId) {
                continue;
            }

            return $this->getOffsetFromPickdropSequence($mainPickdrops, $mainTripStartTime, (int) $item->id);
        }

        return null;
    }

    protected function getMainSubtripPickdrops(int $mainTripId): array
    {
        if (array_key_exists($mainTripId, $this->mainPickdropsCache)) {
            return $this->mainPickdropsCache[$mainTripId];
        }

        $mainSubtrip = $this->subtripModel
            ->where('trip_id', $mainTripId)
            ->where('type', 'main')
            ->first();

        if (!$mainSubtrip) {
            return $this->mainPickdropsCache[$mainTripId] = [];
        }

        return $this->mainPickdropsCache[$mainTripId] = $this->pickdropModel
            ->select('pickdrops.id, pickdrops.stand_id, pickdrops.time, pickdrops.type')
            ->where('trip_id', (int) $mainSubtrip->id)
            ->orderBy('pickdrops.id', 'ASC')
            ->findAll();
    }

    protected function getOffsetFromPickdropSequence(array $pickdrops, string $mainTripStartTime, int $pickStandId): ?int
    {
        $targetStandId = $this->resolveTargetStandId($pickdrops, $pickStandId);

        $lastStopTime  = $mainTripStartTime;
        $currentOffset = 0;

        foreach ($pickdrops as $item) {
            $itemTime     = $this->normalizeTime($item->time ?? '00:00');
            $lastStopTime = $this->normalizeTime($lastStopTime);

            if (strtotime('1970-01-01 ' . $itemTime) < strtotime('1970-01-01 ' . $lastStopTime)) {
                $currentOffset++;
            }
            $lastStopTime = $itemTime;

            if ($pickStandId > 0 && (int) $item->id === $pickStandId) {
                return $currentOffset;
            }

            if ($targetStandId !== null && (int) $item->stand_id === $targetStandId) {
                return $currentOffset;
            }
        }

        return null;
    }

    protected function resolveTargetStandId(array $pickdrops, int $pickStandId): ?int
    {
        if ($pickStandId <= 0) {
            return null;
        }

        foreach ($pickdrops as $item) {
            if ((int) $item->id === $pickStandId) {
                return (int) $item->stand_id;
            }
        }

        $pdRecord = $this->getPickdropById($pickStandId);

        return $pdRecord ? (int) $pdRecord->stand_id : $pickStandId;
    }

    protected function getPickdropById(int $pickStandId): ?object
    {
        if ($pickStandId <= 0) {
            return null;
        }

        if (!array_key_exists($pickStandId, $this->pickdropByIdCache)) {
            $this->pickdropByIdCache[$pickStandId] = $this->pickdropModel
                ->select('id, stand_id, time, type, trip_id')
                ->find($pickStandId) ?: null;
        }

        return $this->pickdropByIdCache[$pickStandId];
    }

    protected function getSubtripPickups(int $subtripId): array
    {
        if ($subtripId <= 0) {
            return [];
        }

        if (array_key_exists($subtripId, $this->subtripPickupsCache)) {
            return $this->subtripPickupsCache[$subtripId];
        }

        return $this->subtripPickupsCache[$subtripId] = $this->pickdropModel
            ->select('id, stand_id, time, type')
            ->where('trip_id', $subtripId)
            ->where('type', 1)
            ->orderBy('id', 'ASC')
            ->findAll();
    }

    protected function getOffsetFromActualPickup(int $subtripId, int $pickStandId, string $mainTripStartTime): ?int
    {
        if ($pickStandId <= 0) {
            return null;
        }

        $pickdrop = $this->getPickdropById($pickStandId);

        if (!$pickdrop) {
            $subtripPickups = $this->getSubtripPickups($subtripId);
            foreach ($subtripPickups as $row) {
                if ((int) $row->stand_id === $pickStandId) {
                    $pickdrop = $row;
                    break;
                }
            }
        }

        if (!$pickdrop) {
            return null;
        }

        $subtripPickups = $this->getSubtripPickups($subtripId);

        if (!empty($subtripPickups)) {
            $offset = $this->getOffsetFromPickdropSequence($subtripPickups, $mainTripStartTime, $pickStandId);
            if ($offset !== null) {
                return $offset;
            }
        }

        $pickupTime = $this->normalizeTime($pickdrop->time ?? '00:00');
        $startTime  = $this->normalizeTime($mainTripStartTime);

        return strtotime('1970-01-01 ' . $pickupTime) < strtotime('1970-01-01 ' . $startTime) ? 1 : 0;
    }

    /**
     * Canonical bus run day for a journeylists row (one physical overnight departure).
     *
     * Prefer tickets.journeydata (boarding day) when available — it uniquely identifies the run.
     */
    public function resolveManifestBusRunDay(
        int $mainTripId,
        int $subtripId,
        string $storedJourneyListDate,
        int $pickStandId,
        ?string $ticketJourneyData = null
    ): string {
        $stored = EgyptTime::toDateString($storedJourneyListDate);

        if ($mainTripId <= 0 || $pickStandId <= 0 || $stored === '1970-01-01') {
            return $stored;
        }

        // journeylists.journeydate is authoritative when it already stores the bus run day.
        if ($this->getBoardingDateFromBusStartDate($mainTripId, $subtripId, $stored, $pickStandId) !== $stored) {
            return $stored;
        }

        $ticketDay = $ticketJourneyData !== null && trim($ticketJourneyData) !== ''
            ? EgyptTime::toDateString($ticketJourneyData)
            : null;

        if ($ticketDay !== null && $ticketDay !== '1970-01-01') {
            return $this->resolveBusRunDayFromTicketJourneyData(
                $mainTripId,
                $subtripId,
                $ticketDay,
                $pickStandId
            );
        }

        return $this->boardingDayToBusRunDate($mainTripId, $subtripId, $stored, $pickStandId);
    }

    /**
     * Bus run day for a journeylists.journeydate value (legacy rows may have boarding day stored).
     */
    public function resolveManifestDateFromJourneyListRow(
        int $mainTripId,
        int $subtripId,
        string $storedJourneyListDate,
        int $pickStandId
    ): string {
        return $this->resolveManifestBusRunDay(
            $mainTripId,
            $subtripId,
            $storedJourneyListDate,
            $pickStandId
        );
    }

    /**
     * @deprecated Use rowMatchesJourneyListManifestSearch() with trip/subtrip/pick context.
     */
    public function rowMatchesJourneyListSearchDate(string $busStartDate, string $boardingDate, string $searchDate): bool
    {
        unset($boardingDate);

        return date('Y-m-d', strtotime($busStartDate)) === date('Y-m-d', strtotime($searchDate));
    }

    /**
     * Journey list = bus run day only. One overnight run appears on exactly one manifest date.
     *
     * @param string|null $ticketJourneyData Boarding day from tickets.journeydata when joined.
     */
    public function rowMatchesJourneyListManifestSearch(
        int $mainTripId,
        int $subtripId,
        string $storedJourneyListDate,
        int $pickStandId,
        string $manifestSearchDate,
        ?string $ticketJourneyData = null
    ): bool {
        $search = date('Y-m-d', strtotime($manifestSearchDate));
        $stored = date('Y-m-d', strtotime($storedJourneyListDate));

        if ($search === '1970-01-01' || $stored === '1970-01-01') {
            return false;
        }

        if ($mainTripId <= 0 || $pickStandId <= 0) {
            return $stored === $search;
        }

        // journeylists.journeydate is the manifest bus run day — trust it when it matches.
        if ($stored === $search) {
            return true;
        }

        // Legacy rows: journeylists stored boarding day instead of bus run day.
        $boardingForSearch = $this->getBoardingDateFromBusStartDate(
            $mainTripId,
            $subtripId,
            $search,
            $pickStandId
        );

        return $stored === $boardingForSearch;
    }

    /**
     * Prefetch journeylists rows for manifest search (±1 day for legacy boarding-day storage).
     *
     * @param \CodeIgniter\Database\BaseBuilder|\CodeIgniter\Model $builder
     */
    public function applyJourneyListManifestDatePrefetch($builder, string $manifestSearchDate): void
    {
        $builder->whereIn(
            'journeylists.journeydate',
            $this->getJourneyListPrefetchDates($manifestSearchDate)
        );
    }

    /**
     * @return list<string>
     */
    public function getJourneyListPrefetchDates(string $manifestSearchDate): array
    {
        $search = date('Y-m-d', strtotime($manifestSearchDate));

        return array_values(array_unique([
            $search,
            date('Y-m-d', strtotime($search . ' +1 day')),
        ]));
    }

    /**
     * Calendar dates to prefetch from journeylists for a manifest search.
     *
     * Includes adjacent days so rows stored with boarding-day journeydate are still loaded.
     *
     * @return string[]
     */
    public function getBusStartDatesForBoardingSearch(string $searchDate): array
    {
        return $this->getTicketSearchMatchDates($searchDate);
    }

    /**
     * Manifest day to store on journeylists at booking time (always bus run day).
     */
    public function resolveManifestDateForJourneyListStorage(
        int $mainTripId,
        int $subtripId,
        string $calendarDate,
        int $pickStandId
    ): string {
        return $this->getManifestDateForJourneylist(
            $mainTripId,
            $subtripId,
            date('Y-m-d', strtotime($calendarDate)),
            $pickStandId
        );
    }

    /**
     * Parse a user search string that is meant to be a calendar date (Y-m-d, d-m-Y, d/m/Y).
     */
    public static function parseSearchDate(string $value): ?string
    {
        $value = trim($value);
        if ($value === '') {
            return null;
        }

        $formats = ['Y-m-d', 'd-m-Y', 'd/m/Y', 'm/d/Y'];
        foreach ($formats as $format) {
            $dt = \DateTime::createFromFormat($format, $value);
            if ($dt && $dt->format($format) === $value) {
                return $dt->format('Y-m-d');
            }
        }

        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
            return date('Y-m-d', strtotime($value));
        }

        return null;
    }

    /**
     * Match tickets.journeydata for ticket-list date search (stored journey date).
     */
    public function applyTicketBoardingDateSearch(\CodeIgniter\Database\BaseBuilder $builder, string $searchDate): void
    {
        $matchDates = $this->getTicketSearchMatchDates($searchDate);

        $builder->groupStart();
        foreach ($matchDates as $i => $d) {
            if ($i === 0) {
                $builder->where('DATE(tickets.journeydata)', $d);
            } else {
                $builder->orWhere('DATE(tickets.journeydata)', $d);
            }
        }
        $builder->groupEnd();
    }

    /**
     * Main route has at least one pickup at or after the scheduled departure time (same calendar evening).
     */
    protected function mainRouteHasSameEveningPickup(int $mainTripId): bool
    {
        $mainTrip = $this->getTrip($mainTripId);
        if (! $mainTrip) {
            return false;
        }

        $schedule          = $this->getSchedule((int) ($mainTrip->schedule_id ?? 0));
        $mainTripStartTime = $this->normalizeTime($schedule->start_time ?? '00:00');
        $mainPickdrops     = $this->getMainSubtripPickdrops($mainTripId);

        foreach ($mainPickdrops as $pickup) {
            if ((int) ($pickup->type ?? 0) !== 1) {
                continue;
            }

            $pickupTime = $this->normalizeTime($pickup->time ?? '00:00');
            if (strtotime('1970-01-01 ' . $pickupTime) >= strtotime('1970-01-01 ' . $mainTripStartTime)) {
                return true;
            }
        }

        return false;
    }

    /**
     * True when every pickup on the booked segment is after midnight relative to the main schedule start.
     */
    protected function segmentHasOnlyAfterMidnightPickups(int $mainTripId, int $subtripId): bool
    {
        $mainTrip = $this->getTrip($mainTripId);
        if (! $mainTrip) {
            return false;
        }

        $schedule          = $this->getSchedule((int) ($mainTrip->schedule_id ?? 0));
        $mainTripStartTime = $this->normalizeTime($schedule->start_time ?? '00:00');
        $pickups           = $this->getBookableSegmentPickups($mainTripId, $subtripId);

        if ($pickups === []) {
            return false;
        }

        foreach ($pickups as $pickup) {
            $pickupTime = $this->normalizeTime($pickup->time ?? '00:00');
            if (strtotime('1970-01-01 ' . $pickupTime) >= strtotime('1970-01-01 ' . $mainTripStartTime)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Pickup points for the segment being booked (main route or subtrip).
     *
     * @return array<int, object>
     */
    protected function getBookableSegmentPickups(int $mainTripId, int $subtripId): array
    {
        $subtrip = $this->subtripModel->find($subtripId);
        if (! $subtrip) {
            return $this->getMainSubtripPickdrops($mainTripId);
        }

        if (($subtrip->type ?? '') === 'main') {
            return $this->getMainSubtripPickdrops($mainTripId);
        }

        $subtripPickups = $this->getSubtripPickups($subtripId);

        return $subtripPickups !== [] ? $subtripPickups : $this->getMainSubtripPickdrops($mainTripId);
    }
}
