<?php



namespace Modules\Ticket\Controllers;



use App\Controllers\BaseController;

use CodeIgniter\API\ResponseTrait;

use Modules\Employee\Models\EmployeeModel;

use Modules\Fleet\Models\FleetModel;

use Modules\Fleet\Models\VehicleModel;

use Modules\Location\Models\LocationModel;

use Modules\Location\Models\StandModel;

use Modules\Paymethod\Models\PaymethodModel;

use Modules\Schedule\Models\ScheduleModel;

use Modules\Tax\Models\TaxModel;

use Modules\Ticket\Models\JourneylistModel;

use Modules\Ticket\Models\PartialpaidModel;

use Modules\Ticket\Models\TicketModel;
use App\Libraries\TripJourneyDateHelper;
use App\Libraries\HoldSeatSupport;
use App\Libraries\BookingInventorySupport;
use App\Libraries\BookingPassengerSupport;
use App\Libraries\SegmentSeatAvailability;
use App\Libraries\SegmentSeatInventoryService;
use App\Libraries\TemporaryBookHoldService;

use Modules\Ticket\Models\PretripModel;

use Modules\Ticket\Models\MaxtimeModel;

use Modules\Trip\Models\FacilityModel;

use Modules\Trip\Models\PickdropModel;

use Modules\Trip\Models\StuffassignModel;

use Modules\Trip\Models\SubtripModel;

use Modules\Trip\Models\TripModel;

use Modules\User\Models\UserDetailModel;

use Modules\User\Models\UserModel;

use Modules\Role\Models\RoleModel;

use Modules\Agent\Models\AgentModel;

use Modules\Agent\Models\Agentcommission;

use Modules\Website\Models\WebsettingModel;

use Modules\Coupon\Models\CoupondiscountModel;

use Modules\Coupon\Models\CouponModel;

use Modules\Trip\Models\RoundtripdiscoundModel;

use Modules\Company\Models\CompanyModel;

use Modules\Ticket\Models\TemporaryBook;


use Modules\HoldSeat\Models\HoldSeatModel;

use Modules\Account\Models\PayagentModel;

use Modules\Ticket\Models\TemporaryBook as TemporaryBookModel;

use Modules\Notification\Models\NotificationModel;
use Modules\Notification\Models\UserNotificationModel;




use App\Libraries\Rolepermission;



use App\Libraries\Ticketmail;

use CodeIgniter\Database\RawSql;

use Exception;



class Ticket extends BaseController

{

    use ResponseTrait;



    protected $Viewpath;

    protected $ticketModel;

    protected $pretripModel;

    protected $tripModel;

    protected $subtripModel;

    protected $stuffassignModel;

    protected $locationModel;

    protected $employeeModel;

    protected $fleetTypeModel;

    protected $scheduleeModel;

    protected $vehicleModel;

    protected $standModel;

    protected $picdropModel;

    protected $facilitypModel;

    protected $taxModel;

    protected $db;

    protected $paymethodModel;

    protected $userModel;

    protected $userDetailModel;

    protected $journeylistModel;

    protected $partialpaidModel;

    protected $maxtimeModel;

    protected $roleModel;

    protected $agentModel;

    protected $agentCommissionModel;



    protected $websettingModel;

    protected $couponModel;

    protected $coupondiscountModel;



    protected $roundtripdiscoundModel;

    protected $CompanyModel;



    protected $TemporaryBook;

    protected $holdSeatModel;

    protected $agentPayment;

    protected $temporaryBookModel;
    
    protected $notificationModel;



    public function __construct()

    {



        $this->Viewpath = "Modules\Ticket\Views";

        $this->ticketModel = new TicketModel();

        $this->pretripModel = new PretripModel();

        $this->tripModel = new TripModel();

        $this->subtripModel = new SubtripModel();

        $this->stuffassignModel = new StuffassignModel();

        $this->locationModel = new LocationModel();

        $this->employeeModel = new EmployeeModel();

        $this->fleetTypeModel = new FleetModel();

        $this->vehicleModel = new VehicleModel();

        $this->scheduleeModel = new ScheduleModel();

        $this->standModel = new StandModel();

        $this->picdropModel = new PickdropModel();

        $this->facilitypModel = new FacilityModel();

        $this->taxModel = new TaxModel();

        $this->db = \Config\Database::connect();

        $this->paymethodModel = new PaymethodModel();



        $this->userModel = new UserModel();

        $this->userDetailModel = new UserDetailModel();



        $this->journeylistModel = new JourneylistModel();

        $this->partialpaidModel = new PartialpaidModel();



        $this->maxtimeModel = new MaxtimeModel();

        $this->roleModel = new RoleModel();



        $this->agentModel = new AgentModel();

        $this->agentCommissionModel = new Agentcommission();



        $this->websettingModel = new WebsettingModel();



        $this->coupondiscountModel = new CoupondiscountModel();

        $this->couponModel = new CouponModel();



        $this->roundtripdiscoundModel = new RoundtripdiscoundModel();

        $this->CompanyModel = new CompanyModel();

        $this->TemporaryBook = new TemporaryBook();

        $this->holdSeatModel = new HoldSeatModel();

        $this->agentPayment = new PayagentModel();

        $this->temporaryBookModel = new TemporaryBookModel();
        
        
        $this->notificationModel = new NotificationModel();

        $this->userNotificationModel = new UserNotificationModel();

    }



    function new()

    {



        $this->session->remove('pick_location_id');

        $this->session->remove('drop_location_id');

        $this->session->remove('filterjourneydate');

        $this->session->remove('filterreturndate');

        $this->session->remove('fleet_id');



        $this->session->remove('subtripId');

        $this->session->remove('seatnumbers');

        $this->session->remove('totalseat');

        $this->session->remove('journeydate');

        $this->session->remove('aseat');

        $this->session->remove('spseat');

        $this->session->remove('vpseat');

        $this->session->remove('cseat');

        $this->session->remove('totalprice');

        $this->session->remove('tax');

        $this->session->remove('grandtotal');

        $this->session->remove('pickstand');

        $this->session->remove('dropstand');

        $this->session->remove('isrountrip');

        $this->session->remove('discountPercent');

        $this->session->remove('roundtrip_discount');



        $data['pick_location_id'] = null;

        $data['drop_location_id'] = null;

        $data['filterjourneydate'] = null;

        $data['filterreturndate'] = null;

        $data['fleet_id'] = null;



        $data['filterpath'] = $this->Viewpath;

        $data['location'] = $this->locationModel->findAll();

        $data['fleet_type'] = $this->fleetTypeModel->findAll();



        $data['module'] =    lang("Localize.ticket_booking");

        $data['title']  =    lang("Localize.book_ticket");



        $heading = lang("Localize.search") . ' ' . lang("Localize.ticket");

        $data['pageheading'] = $heading;



        $data['company_id'] = 0;

        $data['companies'] = $this->CompanyModel->findAll();



        echo view($this->Viewpath . '\ticket/new', $data);

    }



    public function findtrip()

    {

        $t = $this->request->getVar();

        $websetting = $this->websettingModel->first();



        // Get request vars

        $pick_location_id  = $this->request->getVar('pick_location_id') ?: session('pick_location_id');

        $drop_location_id  = $this->request->getVar('drop_location_id') ?: session('drop_location_id');

        $filterjourneydate = $this->request->getVar('filterjourneydate') ?: session('filterjourneydate');

        $filterreturndate  = $this->request->getVar('filterreturndate') ?: session('filterreturndate');

        $fleet_id          = $this->request->getVar('fleet_id') ?: session('fleet_id');

        

        $company_id = session()->get('company_id') ?? 0;

        if(($this->request->getVar('company_id') || $this->request->getVar('company_id') == 0) && session()->get('role_id') == "1"){

            $company_id = $this->request->getVar('company_id') ?? 0;

        } else {

            if(session('search_company_id')){

                $company_id = session('search_company_id');

            }

        }
        
        $ticket_token = $this->request->getVar('ticket_token');
        if ($ticket_token) {
            session()->set('ticket_token', $ticket_token);
        }

        // 

        $journeyDate = BookingInventorySupport::normalizeCalendarDate((string) $filterjourneydate);
        if (! BookingInventorySupport::canBookBackdatedTrips() && BookingInventorySupport::isBackdatedCalendarDate($journeyDate)) {
            return redirect()->back()->with('fail', 'Searching for backdated trips is not allowed for your role.');
        }

        $journeyDayOfWeek = date('N', strtotime($filterjourneydate));



        $this->session->remove('pick_location_id');

        $this->session->remove('drop_location_id');

        $this->session->remove('filterjourneydate');

        $this->session->remove('filterreturndate');

        $this->session->remove('fleet_id');



        $this->session->remove('subtripId');

        $this->session->remove('seatnumbers');

        $this->session->remove('totalseat');

        $this->session->remove('journeydate');

        $this->session->remove('aseat');

        $this->session->remove('spseat');

        $this->session->remove('vpseat');

        $this->session->remove('cseat');

        $this->session->remove('totalprice');

        $this->session->remove('tax');

        $this->session->remove('grandtotal');

        $this->session->remove('pickstand');

        $this->session->remove('dropstand');

        $this->session->remove('isrountrip');

        $this->session->remove('discountPercent');

        $this->session->remove('roundtrip_discount');

        $this->session->remove('search_company_id');



        $tripData['pick_location_id'] =  $pick_location_id;

        $tripData['drop_location_id'] = $drop_location_id;

        $tripData['filterjourneydate'] =  $filterjourneydate;

        $tripData['filterreturndate'] = $filterreturndate;

        $tripData['fleet_id'] = $fleet_id;

        $tripData['search_company_id'] = $company_id;



        $this->session->set($tripData);



        $monthcomparedate = get_egypt_time('+1 month')->format('Y-m-d');



        // if ($filterjourneydate > $monthcomparedate) {

        //     return redirect()->route('new-ticket')->with("fail", "No advance booking for this day");

        // }



        $data = array('trips' => [], 'in_holiday' => [], 'to_open' => [], 'inactive' => [], 'you_may_like' => []);

        $data['pick_location_id'] = $pick_location_id;

        $data['drop_location_id'] = $drop_location_id;

        $data['filterjourneydate'] = $filterjourneydate;

        $data['filterreturndate'] = $filterreturndate;

        $data['fleet_id'] = $fleet_id;

        $data['company_id'] = $company_id;



        $data['filterpath'] = $this->Viewpath;

        // $data['location'] = $this->locationModel->findAll();
        
        // $data['location'] = $this->locationModel->orderBy('name', 'DESC')->findAll();
        
        $data['location'] = $this->locationModel
    ->orderBy("(name REGEXP '^[ء-ي]')", "DESC", false)
    ->orderBy("name COLLATE utf8mb4_unicode_520_ci", "ASC", false)
    ->findAll();



        $data['fleet_type'] = $this->fleetTypeModel->findAll();

        $data['companies'] = $this->CompanyModel->findAll();



        if (strpos($company_id, ',')) { 

            $company_ids = explode(',', $company_id);

        } else {

            $company_ids = [$company_id];

        }



        $allSubTrips = $this->getAllTrip($pick_location_id, $drop_location_id, $fleet_id, $company_ids);



        $journeyDateHelper = new \App\Libraries\TripJourneyDateHelper();
        $overnightRun      = new \App\Libraries\OvernightTripRunSupport($journeyDateHelper);

        foreach ($allSubTrips as $subTrip) {
            \App\Libraries\TripDisplaySupport::applyParentTripDistanceAndHours($subTrip);
            $eligiblePickStands = $overnightRun->pickStandsForBoardingSearchDate(
                (int) $subTrip->tripid,
                (int) $subTrip->subtripId,
                $journeyDate
            );
            if ($eligiblePickStands === []) {
                continue;
            }

            $pickStandId = $eligiblePickStands[0];

            $departureDatesForSearch = BookingInventorySupport::resolveSearchDepartureDates(
                (int) $subTrip->tripid,
                (int) $subTrip->subtripId,
                $journeyDate,
                $pickStandId
            );
            $busStartDate = $departureDatesForSearch['bus_start'];
            $busStartDayOfWeek = date('N', strtotime($busStartDate));


            if ($subTrip->status == 0) {

                // subtrip is inactive

                $data['inactive'][] = $subTrip;

                continue;

            }



            if (strtotime($busStartDate) < strtotime($subTrip->startdate)) {

                // subtrip yet not begin

                $data['to_open'][] = $subTrip;

                continue;

            }

            // if ($busStartDate ==  $subTrip->offdates) {

            $cleanedoffdates = preg_replace('/\s+/', '', $subTrip->offdates);

            if (in_array($busStartDate,explode(",", $cleanedoffdates))) {

                // subtrip is off today

                $data['in_holiday'][] = $subTrip;

                continue;

            }



            if ($this->isDateInRange($busStartDate, $subTrip->startdateinrange, $subTrip->enddateinrange)) {

                // subtrip is not in range

                $data['in_holiday'][] = $subTrip;

                continue;

            }



            $offdateranges = json_decode($subTrip->offdateranges, true);

            $isInAnyRange = false;

            if (is_array($offdateranges) && !empty($offdateranges)) {

                foreach ($offdateranges as $range) {

                    $start = $range['start'] ?? null;

                    $end = $range['end'] ?? null;



                    if ($start && $end && $this->isDateInRange($busStartDate, $start, $end)) {

                        $isInAnyRange = true;

                        break;

                    }

                }

            }



            if ($isInAnyRange) {

                $data['in_holiday'][] = $subTrip;

                continue;

            }





            if (!$this->isDateInRange($busStartDate, $subTrip->repetedstartdate, $subTrip->repetedenddate)) {

                if($busStartDate != date('Y-m-d',strtotime($subTrip->startdate))){

                    //start date and not between start and edn date

                    $data['in_holiday'][] = $subTrip;

                    continue;    

                }

                

            }



            $today = new \DateTime(date('Y-m-d',strtotime($subTrip->repetedstartdate)));



            $busStartDateObj = new \DateTime(date('Y-m-d', strtotime($busStartDate)));

            $interval = $today->diff($busStartDateObj);

            $bookingToday = new \DateTime(egypt_today());
            $bookingWindowInterval = $bookingToday->diff($busStartDateObj);

            if (
                $subTrip->maxdayforbooking > 0
                && $busStartDateObj >= $bookingToday
                && ($subTrip->maxdayforbooking < $bookingWindowInterval->days)
            ) {

                //not allowed to book tickets

                $data['in_holiday'][] = $subTrip;

                continue;

            }



            if ($this->isDateInRange($busStartDate, $subTrip->repetedstartdate, $subTrip->repetedenddate)) {

                if($busStartDate != date('Y-m-d',strtotime($subTrip->startdate))){

                    if($subTrip->repeted_after > 0 && $interval->days > 0){

                        if ($interval->days % ($subTrip->repeted_after + 1) != 0){
                            //start date and not between start and edn date

                            $data['in_holiday'][] = $subTrip;

                            continue;

                        }

                    }

                    

                }

                

            }



            if (@in_array($busStartDayOfWeek, explode(',', $subTrip->weekend))) {

                // subtrip is on holiday

                $data['in_holiday'][] = $subTrip;

                continue;

            }



            if (($subTrip->pick_location_id != $pick_location_id) || ($subTrip->drop_location_id != $drop_location_id)) {

                // subtrip is related to selected locations

                $subTrip->journeydate = $filterjourneydate;
                $data['you_may_like'][] = $subTrip;

                continue;

            }

            $subtripInfo = $this->subtripModel->find($subTrip->id);

            $inventoryService = new \App\Libraries\SegmentSeatInventoryService($this->db);
            $unavailableSeats = $inventoryService->collectUnavailableSeatNumbers(
                (int) $subTrip->subtripId,
                $filterjourneydate,
                $pickStandId,
                null,
                $subtripInfo->pick_location_id,
                $subtripInfo->drop_location_id
            );
            $subTrip->bookseatCount = count($unavailableSeats);

            $pickdrop = $this->picdropModel->select('pickdrops.id as pickdropid,pickdrops.*,stands.*, stands.name as stand_name')

                ->join('stands', 'stands.id = pickdrops.stand_id')

                ->where('trip_id', $subtripInfo->id)
                
                ->orderBy('pickdrops.id','ASC')

                ->findAll();

            if(empty($pickdrop)){

                $pickdrop = $this->picdropModel->select('pickdrops.id as pickdropid,pickdrops.*,stands.*, stands.name as stand_name')

                    ->join('stands', 'stands.id = pickdrops.stand_id')

                    ->where('trip_id', $subtripInfo->trip_id)
                    
                    ->orderBy('pickdrops.id','ASC')

                    ->findAll();

            }

            // print_r($pickdrop);

            $pickup_time = null;

            $drop_time = null;



            // foreach ($pickdrop as $item) {

            //     if ($item->type == 1) {

            //         $pickup_time = $item->time;

            //     } elseif ($item->type == 0) {

            //         $drop_time = $item->time;

            //     }

            // }

            $pickup_time = null;

            $drop_time = null;



            foreach ($pickdrop as $item) {

                if ($item->type == 1) {

                    // Smallest pickup time

                    // if (is_null($pickup_time) || strtotime($item->time) < strtotime($pickup_time)) {
                    if (is_null($pickup_time)) {

                        $pickup_time = $item->time;

                    }

                } elseif ($item->type == 0) {

                    // Largest drop time

                    // if (is_null($drop_time) || strtotime($item->time) > strtotime($drop_time)) {
                    if (is_null($drop_time)) {

                        $drop_time = $item->time;

                    }

                }

            }





            $subTrip->pickup_time = $pickup_time;

            $subTrip->drop_time = $drop_time;

            // The 'journeydate' displayed to the user should be the date they actually board!
            $subTrip->journeydate = $filterjourneydate; 
            // We store the physical bus start date separately so the booking process knows which run to lock.
            $subTrip->busStartDate = $busStartDate;
            $subTrip->eligiblePickStandIds = $eligiblePickStands;

            // Agents/backend users: hide trip 1 hour after departure. Super admin + company admin may still book.
            if (! BookingInventorySupport::canBookBackdatedTrips() && ! empty($pickup_time)) {
                $egyptCurrentTime = get_egypt_time();

                $runContext = $overnightRun->resolveBoardingSearch(
                    (int) $subTrip->tripid,
                    (int) $subTrip->subtripId,
                    $filterjourneydate,
                    $pickStandId
                );
                $departureDateTimeStr = $runContext['boarding_datetime'];
                try {
                    $departureDateTime = new \DateTime($departureDateTimeStr, new \DateTimeZone("Africa/Cairo"));
                    
                    // The trip is available until exactly 1 hour after the departure time
                    $oneHourAfterDeparture = (clone $departureDateTime)->modify('+1 hour');
                    
                    if ($egyptCurrentTime >= $oneHourAfterDeparture) {
                        continue; // Hide the trip
                    }
                } catch (\Exception $e) {
                    // Ignore parsing errors
                }
            }
            
            $data['trips'][] = $subTrip;

            // print_r($data);

            // exit;

        }



        $data['taxtype'] = $websetting->tax_type;

        $data['module'] = lang("Localize.ticket_booking");

        $data['title'] = lang("Localize.book_ticket");

        $data['pageheading'] = lang("Localize.book") . ' ' . lang("Localize.ticket");

        $data['triptype'] = lang("Localize.single") . ' ' . lang("Localize.trip");

        //echo "<pre>";print_r($data);exit;

        return view($this->Viewpath . '\ticket\index', $data);

    }



    public function getAllTrip($pickLocationId, $dropLocationId, $fleetId = null, $company_id)

    {

        // bind search query

        $this->subtripModel

            ->select('trips.id as tripid, trips.*, ' . \App\Libraries\TripDisplaySupport::parentMetricsSelectFragment() . ', fleets.*, schedules.*, vehicles.*, subtrips.id as subtripId, subtrips.*,companies.name as company, trip_bus_types.name AS trip_bus_type, trip_category.name AS trip_category_name')



            ->join('trips', 'trips.id = subtrips.trip_id')

            ->join('fleets', 'fleets.id = trips.fleet_id')

            ->join('schedules', 'schedules.id = trips.schedule_id')

            ->join('vehicles', 'vehicles.id = trips.vehicle_id',"left")

            ->join('companies', 'companies.id = trips.company_id',"left")

            ->join('trip_bus_types', 'trip_bus_types.id = trips.trip_bus_type_id',"left")

            ->join('trip_category', 'trip_category.id = trips.trip_category_id',"left")



            ->groupStart()

                ->groupStart()

                    ->where('subtrips.pick_location_id', $pickLocationId)

                    ->where('subtrips.drop_location_id', $dropLocationId)

                ->groupEnd()



                ->orGroupStart()

                    ->whereIn('subtrips.trip_id', function ($subQuery) use ($pickLocationId, $dropLocationId) {

                        $subQuery

                            ->select('sm.trip_id')

                            ->from('subtrips sm')

                            ->where('sm.pick_location_id', $pickLocationId)

                            ->where('sm.drop_location_id', $dropLocationId)

                            ->where('sm.type', 'subtrip');

                    })

                    ->where('subtrips.type', 'main')

                ->groupEnd()

            ->groupEnd()

            ->where('subtrips.status', 1)

            ->where('trips.status', 1)

            ->where('companies.status', 1);



            if(is_array($company_id) && session()->get('role_id') != "1"){

                $this->subtripModel->whereIn('trips.company_id', $company_id);

            } else {

                $nonEmptyCompanyId = array_filter($company_id);

                if(!empty($nonEmptyCompanyId)){

                    $this->subtripModel->whereIn('trips.company_id', $company_id);

                }

                

            }



            $this->subtripModel->orderBy('subtrips.id', 'DESC');



        $fleetId && $this->subtripModel->where('trips.fleet_id', $fleetId);

        // $query = $this->subtripModel->builder()->getCompiledSelect();dd($query);

        return $this->subtripModel->findAll();

    }

    public function getSingleTrip($subTripId, $journeydate)
    {
        (new \App\Libraries\TemporaryBookHoldService())->cleanupExpiredHolds();
        $maxtimeRow = $this->maxtimeModel->first();
        $maxtime = 60 * (int) ($maxtimeRow->maxtime ?? 15);

        $subtripInfo = $this->subtripModel->find($subTripId);
        if (!$subtripInfo) {
            return $this->response->setJSON(['status' => "error", 'message' => "Subtrip not found"]);
        }
        $mainTripId = $subtripInfo->trip_id;

        $overnightRun = new \App\Libraries\OvernightTripRunSupport();
        // Admin expand: list every pickup on this subtrip; seat inventory keys off boarding search date + pickstand.
        $eligiblePickStands = $overnightRun->allPickupStandIdsForSubtrip($mainTripId, (int) $subTripId);
        if ($eligiblePickStands === []) {
            return $this->response->setJSON(['status' => 'error', 'message' => 'No pickup points configured for this trip.']);
        }

        // Seat inventory uses pickstand once the agent selects a boarding point.
        // Initial expand: return booking form + pickdrops; defer seat map until pickstand is sent.
        $pickStandId = (int) ($this->request->getVar('pickstand') ?? 0);
        $requiresPickstand = $overnightRun->clientMustSelectPickstand($mainTripId, (int) $subTripId);
        $awaitingPickstand = $requiresPickstand && $pickStandId <= 0;

        if (! $awaitingPickstand && $pickStandId <= 0) {
            $pickStandId = $eligiblePickStands[0] ?? BookingInventorySupport::resolveDefaultPickStandId($mainTripId, (int) $subTripId);
        }

        $cleanupPickStand = $pickStandId > 0
            ? $pickStandId
            : ($eligiblePickStands[0] ?? BookingInventorySupport::resolveDefaultPickStandId($mainTripId, (int) $subTripId));
        // Search date is passenger boarding day; inventory keys off resolved service date.
        $departureDatesForCleanup = BookingInventorySupport::resolveSearchDepartureDates(
            (int) $mainTripId,
            (int) $subTripId,
            BookingInventorySupport::normalizeCalendarDate((string) $journeydate),
            $cleanupPickStand
        );
        $busStartDate = $departureDatesForCleanup['bus_start'];

        $unpaidBuilder = $this->ticketModel->builder();
        $unpaidBuilder->select('tickets.id, tickets.created_at, tickets.booking_id, tickets.journeydata, tickets.pick_stand_id, tickets.subtrip_id')
            ->where('trip_id', $mainTripId)
            ->where('payment_status', 'unpaid')
            ->where('cancel_status', 0)
            ->where('refund', 0);
        BookingInventorySupport::applyTicketJourneyDateFilter($unpaidBuilder, (int) $mainTripId, $departureDatesForCleanup);
        $getData = $unpaidBuilder->get()->getResult();

        foreach ($getData as $key => $delvalue) {
            if (! BookingInventorySupport::ticketBelongsToBusRun(
                (int) $mainTripId,
                (int) ($delvalue->subtrip_id ?? 0) ?: (int) $subTripId,
                (string) ($delvalue->journeydata ?? ''),
                (int) ($delvalue->pick_stand_id ?? 0),
                $busStartDate
            )) {
                continue;
            }

            $cratetime = get_egypt_time($delvalue->created_at)->getTimestamp();
            $timenow = get_egypt_time()->getTimestamp();
            if (($timenow - $cratetime) > $maxtime) {
                $this->ticketModel->where('id', $delvalue->id)->set(['cancel_status' => 1])->update();
                $bookingId = $this->ticketModel->find($delvalue->id);
                $this->journeylistModel->where('booking_id', $bookingId->booking_id)->delete();
            }
        }

        $flatBooked = [];
        $holdseat   = [];
        $blockseat  = [];
        $boardingCalendarDate = $departureDatesForCleanup['boarding'];
        $busStartDate         = $departureDatesForCleanup['bus_start'];

        if (! $awaitingPickstand) {
            // Same segment inventory as booking validation (isAlreadybookedsubtrip / checkSeats).
            $ignoreHoldToken = $this->request->getCookie('_chkseats_token') ?? null;
            $inventory = (new \App\Libraries\SegmentSeatInventoryService($this->db))->collectSeatBuckets(
                (int) $subTripId,
                $journeydate,
                $pickStandId,
                is_string($ignoreHoldToken) && $ignoreHoldToken !== '' ? $ignoreHoldToken : null,
                false
            );

            $flatBooked = $inventory['booked'];
            $holdseat   = $inventory['hold'];
            $blockseat  = $inventory['block'];
            $boardingCalendarDate = $inventory['boarding_date'] ?: $boardingCalendarDate;
            $busStartDate         = $inventory['bus_start_date'] ?: $busStartDate;
        }

        $departureDates = BookingInventorySupport::resolveSearchDepartureDates(
            (int) $mainTripId,
            (int) $subTripId,
            BookingInventorySupport::normalizeCalendarDate((string) $journeydate),
            $pickStandId > 0
                ? $pickStandId
                : ($eligiblePickStands[0] ?? BookingInventorySupport::resolveDefaultPickStandId($mainTripId, (int) $subTripId))
        );
        $holdMeta = HoldSeatSupport::getActiveBlockedSeatNumbersWithMeta(
            (int) $mainTripId,
            (int) $subTripId,
            $departureDates['all']
        );
        // Admin hold_seats → yellow (blockseat). Temp checkout holds stay in holdseat (green).

        // Build locations and stands (all pickups + drops for this subtrip; no boarding-date filter in admin).
        $pickdrop = $this->loadPickdropsForBookingForm((int) $subtripInfo->id, (int) $mainTripId);

        $tax = $this->taxModel->where('status', 1)->findAll();

        // Build subtrip info
        $subtrips = $this->subtripModel->select('trips.id as tripid,trips.*,fleets.*,schedules.*,vehicles.*,subtrips.id as subtripId,subtrips.*, fleets.id as fleet_id')
            ->join('trips', 'trips.id = subtrips.trip_id')
            ->join('fleets', 'fleets.id = trips.fleet_id')
            ->join('schedules', 'schedules.id = trips.schedule_id')
            ->join('vehicles', 'vehicles.id = trips.vehicle_id',"left")
            ->where('subtrips.status', 1)
            ->where('subtrips.id', $subTripId)
            ->findAll();

        if(isset($subtrips[0]->subtrip_seatclass) && !empty($subtrips[0]->subtrip_seatclass)){
            $seatClasses = $subtrips[0]->subtrip_seatclass ? json_decode($subtrips[0]->subtrip_seatclass, true) : "";        
        }else{
            $seatClasses = $subtrips[0]->seatclass ? json_decode($subtrips[0]->seatclass, true) : "";        
        }

        $adultFair = $subtrips[0]->adult_fair ?? "";

        return $this->response->setJSON([
            'status' => "success",
            'response' => 200,
            'requires_pickstand' => $requiresPickstand,
            'eligible_pick_stands' => $eligiblePickStands,
            'subtrips' => json_encode($subtrips),
            'pickdrop' => json_encode($pickdrop),
            'tax' => json_encode($tax),
            'bookseat' => json_encode(implode(',', array_values(array_unique($flatBooked)))),
            'holdseat' => json_encode(implode(',', array_values(array_unique($holdseat)))),
            'blockseat' => json_encode(implode(',', $blockseat)),
            'hold_debug' => json_encode(array_merge(\App\Libraries\EgyptTime::clockDiagnostics(), [
                'boarding_date' => $boardingCalendarDate,
                'bus_start_date' => $busStartDate,
                'journey_dates_checked' => $departureDates['all'],
                'matched_hold_rows' => $holdMeta['matched_rows'],
                'blocked_seats' => $blockseat,
                'recent_holds_for_journey' => HoldSeatSupport::diagnoseHoldSeatLookup(
                    (int) $mainTripId,
                    (int) $subTripId,
                    $departureDates['all']
                ),
                'hint' => $blockseat === []
                    ? 'Need hold: trip 74, subtrip 228 in CSV, journey covers boarding/bus dates, status=1, Egypt now between startdate and enddate.'
                    : 'ok',
            ])),
            'seatClass' => json_encode($seatClasses),
            'adultfare' => $adultFair,
        ]);
    }



    public function getSeatLayout()

    {

        $subTripId = $this->request->getVar('id');

        $fleetId = $this->request->getVar('fleet_id');

        $bookedSeats = $this->request->getVar('booked');

        $holdSeats = $this->request->getVar('hold');

        $blockSeats = $this->request->getVar('block');

        $normalizeSeatList = static function ($list): array {
            $out = [];
            foreach ((array) $list as $s) {
                if ($s === null || $s === '') {
                    continue;
                }
                $out[] = trim((string) $s);
            }

            return array_values(array_unique($out));
        };

        $bookedSeats = $normalizeSeatList($bookedSeats);

        $holdSeats = $normalizeSeatList($holdSeats);

        $blockSeats = $normalizeSeatList($blockSeats);

        $fleetInfo = $this->fleetTypeModel->find($fleetId);

        $fleetTotalSeat = $fleetInfo->total_seat;

        // print_r($fleetInfo);die;

        // build seat layout vars

        $seatLayout     = $fleetInfo->layout;

        $seatLayoutArr  = array_filter(explode('-', $seatLayout));

        $totalSeatInRow = array_sum($seatLayoutArr);

        $tSeatRowWithB  = $totalSeatInRow + count($seatLayoutArr) - 3;

        // print_r($tSeatRowWithB);die;

        $seatNumbers    = array_filter(explode(',', $fleetInfo->seat_number));

        // remove last seat

        // if fleet has last seat

        $lastSeat = '';

        // $fleetInfo->last_seat && $lastSeat = array_pop($seatNumbers);

        $seatinfo['forth_seat'] = $fleetInfo->forth_seat;

        $seatinfo['lastSheet'] = $fleetInfo->last_seat;

        $seatinfo['wc'] = $fleetInfo->wc;







        // build seat group/row

        $seatRowGroup = array_chunk($seatNumbers, $totalSeatInRow);

        $seatRows = array_map(function ($singleRow) use ($seatLayoutArr, $tSeatRowWithB, $bookedSeats, $holdSeats, $blockSeats) {

        // print_r($tSeatRowWithB);die;

            $newSingleRow = array();

            $currentTotalIndex = 0;



            foreach ($singleRow as $seatRowIndex => $singleSeat) {

                $seatRowIndex++;

                $seatKey = trim((string) $singleSeat);

                $isBooked = in_array($seatKey, $bookedSeats, true) ? 1 : 0;
                if (!$isBooked) {
                    foreach ($bookedSeats as $bs) {
                        if (trim((string) $bs) !== '' && (string) $bs === $seatKey) {
                            $isBooked = 1;
                            break;
                        }
                    }
                }
                $isBlock = in_array($seatKey, $blockSeats, true) ? 1 : 0;
                $isHold = (! $isBooked && ! $isBlock && in_array($seatKey, $holdSeats, true)) ? 1 : 0;

                $newSingleRow[] = array(

                    'seatName' => $singleSeat,

                    'isBooked' => $isBooked,

                    'isHold' => $isHold,

                    'isBlock' => $isBlock,

                );



                if (($seatRowIndex != count($singleRow)) && (current($seatLayoutArr) + $currentTotalIndex == $seatRowIndex)) {

                    $newSingleRow[] = '';

                    $currentTotalIndex += current($seatLayoutArr);

                    array_shift($seatLayoutArr);

                }

            }



            return array_pad($newSingleRow, $tSeatRowWithB, '');

        }, $seatRowGroup);



// print_r(count($seatRows));

// exit;

if(count($seatRows[count($seatRows)-1]) != count($seatRows[1])){

    $seatRows[count($seatRows)-2] = array_merge($seatRows[count($seatRows)-2],$seatRows[count($seatRows)-1]);

    unset($seatRows[count($seatRows)-1]);

}

// print_r($seatRows[count($seatRows)-2]);

// exit;

        // $lastRow = end($seatRows);

        // if ($previousRowIndex >= 0) {

        //     $previousRow = &$seatRows[$previousRowIndex];

        //     $outstandingSeats = array_splice($previousRow, $totalSeatInRow);

        //     $lastRow = array_merge($lastRow, $outstandingSeats);

        //     $seatRows[count($seatRows) - 1] = $lastRow;

        // }



        // print_r($seatRows);die;

        if ($lastSeat !== '') {

            // last seat exists

            // build last seat info

            $lastKey = trim((string) $lastSeat);

            $lb = in_array($lastKey, $bookedSeats, true) ? 1 : 0;
            if (!$lb) {
                foreach ($bookedSeats as $bs) {
                    if (trim((string) $bs) !== '' && (string) $bs === $lastKey) {
                        $lb = 1;
                        break;
                    }
                }
            }
            $lastBlock = in_array($lastKey, $blockSeats, true) ? 1 : 0;
            $lh = (! $lb && ! $lastBlock && in_array($lastKey, $holdSeats, true)) ? 1 : 0;

            $lastSeatInfo =  array(

                'seatName' => $lastSeat,

                'isBooked' => $lb,

                'isHold' => $lh,

                'isBlock' => $lastBlock,

            );

            

            if (($lastBlankSpace = array_search('', end($seatRows))) !== false) {

                // an blank space exists in last row

                // last seat place into the last blank space

                $seatRows[count($seatRows) - 1][$lastBlankSpace] = $lastSeatInfo;

            } else {

                // create new row and last seat place into it

                $seatRows[][] = $lastSeatInfo;

            }

        }

        

        // print_r($seatRows);die;



        try {

            //if()

            $filePath = sprintf("%s\\ticket\\seatlayouts\\%s", $this->Viewpath, $seatLayout);

            return view($filePath, compact('subTripId', 'seatRows', 'totalSeatInRow'));

        } catch (\Throwable $e) {

            // print_r($filePath);die;

            if($seatLayout =='4-1'){

                // print_r($seatRows);

                // exit;

                $filePath = sprintf("%s\\ticket\\seatlayouts\\layoutfinal2", $this->Viewpath);

                return view($filePath, compact('subTripId', 'seatRows', 'totalSeatInRow','fleetTotalSeat','seatinfo'));

            }else if($seatLayout =='1-1'){

                $filePath = sprintf("%s\\ticket\\seatlayouts\\layoutfinal1-1", $this->Viewpath);

                return view($filePath, compact('subTripId', 'seatRows', 'totalSeatInRow','fleetTotalSeat','seatinfo'));

            }else if($seatLayout =='1-2' || $seatLayout =='2-1'){

                $filePath = sprintf("%s\\ticket\\seatlayouts\\layoutfinal1-2", $this->Viewpath);

                return view($filePath, compact('subTripId', 'seatRows', 'totalSeatInRow','fleetTotalSeat','seatinfo'));

            }else{

                $filePath = sprintf("%s\\ticket\\seatlayouts\\default3", $this->Viewpath);

            }

            return view($filePath, compact('subTripId', 'seatRows', 'totalSeatInRow', 'fleetTotalSeat','seatinfo'));

        }

    }



    /**
     * Whether the logged-in user may apply a manual discount on admin ticket booking.
     */
    protected function canApplyBookingDiscount(): bool
    {
        $roleId = (int) session()->get('role_id');
        if ($roleId === 1) {
            return true;
        }

        return (new Rolepermission())->create('booking_discount');
    }

    /**
     * Force discount to zero when the user lacks booking_discount permission.
     */
    protected function sanitizeBookingDiscount($discount)
    {
        if (!$this->canApplyBookingDiscount()) {
            return 0;
        }

        return max(0, (float) ($discount ?? 0));
    }

    public function booking()

    {

        if (empty($this->request->getVar())) {

            $failMsg = session()->getFlashdata('fail') ?: 'Session expired!';

            return redirect()->route('new-ticket')->with("fail", $failMsg);

        }



        $data['returndate'] = $this->request->getVar('returndate');
        $requestedJourneyDate = BookingInventorySupport::normalizeCalendarDate((string) $this->request->getVar('journeydate'));
        if (! BookingInventorySupport::canBookBackdatedTrips() && BookingInventorySupport::isBackdatedCalendarDate($requestedJourneyDate)) {
            return redirect()->route('new-ticket')->with('fail', 'Booking for backdated trips is not allowed for your role.');
        }



        $data['seatnumbers'] = $this->request->getVar('seatnumbers');

        $array = explode(",", $data['seatnumbers']);

        $daynamic = count($array);

        

        if (!empty($data['returndate'])) {



            $grandtotal = $this->request->getVar('grandtotal');

            // if($this->request->getVar('subtripId') == 209){


            $CompanyId = $this->subtripModel->select('trips.company_id as companyId')

            ->join('trips', 'trips.id = subtrips.trip_id')

            ->where('subtrips.status', 1)

            ->where('subtrips.id', $this->request->getVar('subtripId'))

            ->first();

            $discount = $this->roundtripdiscoundModel->where('status', 1)->where('company_id',$CompanyId->companyId)->first();

            // }
            $discountMoney = 0;

            $discountPercent = 0;

            if (!empty($discount)) {

                $roleId = (int) ($this->session->get('role_id') ?? 0);
                $isAdminSideBooking = ($roleId !== 3);
                $discountPercent = $isAdminSideBooking
                    ? (float) ($discount->discountrate ?? 0)
                    : (float) ($discount->onlinediscountrate ?? $discount->discountrate ?? 0);

                $caltulateDiscont = (float) ($discountPercent / 100) * $grandtotal;

                $discountMoney = (float)  $grandtotal - (float)$caltulateDiscont;

            } else {

                $discountMoney = $grandtotal;

                $discountPercent = 0;

                $caltulateDiscont = 0;

            }



            $singleTripData['subtripId'] =  $this->request->getVar('subtripId');

            $singleTripData['seatnumbers'] = $this->request->getVar('seatnumbers');

            $singleTripData['totalseat'] =  $daynamic;

            $singleTripData['journeydate'] = $this->request->getVar('journeydate');

            $singleTripData['aseat'] = $this->request->getVar('aseat');

            $singleTripData['spseat'] = $this->request->getVar('spseat');

            $singleTripData['vpseat'] = $this->request->getVar('vpseat')??"";

            $singleTripData['cseat'] = $this->request->getVar('cseat');

            $singleTripData['totalprice'] = $this->request->getVar('totalprice');

            $singleTripData['tax'] = $this->request->getVar('tax');

            $singleTripData['grandtotal'] = $discountMoney;

            $singleTripData['pickstand'] = $this->request->getVar('pickstand');

            $singleTripData['dropstand'] = $this->request->getVar('dropstand');

            $singleTripData['discountPercent'] = $discountPercent;

            $singleTripData['roundtrip_discount'] = $caltulateDiscont;



            $this->session->set($singleTripData);



            return redirect()->route('roundfindtrip-ticket');

        } else {

            $data['filterpath'] = $this->Viewpath;

            $builder = $this->db->table('country');

            $query = $builder->get();

            $data['country'] = $query->getResult();



            $data['subtripId'] = $this->request->getVar('subtripId');

            $data['seatnumbers'] = $this->request->getVar('seatnumbers');



            $array = explode(",", $data['seatnumbers']);



            $daynamic = count($array);

            $data['dynamicfield'] = $daynamic - 1;



            $data['totalseat'] = $daynamic;



            $data['journeydate'] = $this->request->getVar('journeydate');





            $data['aseat'] = $this->request->getVar('aseat');

            $data['spseat'] = $this->request->getVar('spseat');

            $data['vpseat'] = $this->request->getVar('vpseat')??"";

            $data['cseat'] = $this->request->getVar('cseat');



            $data['totalprice'] = $this->request->getVar('totalprice');

            $data['tax'] = $this->request->getVar('tax');







            if ($this->session->has('discountPercent')) {

                $percentDiscount = $this->session->get('discountPercent');

                $getGrandtotal = $this->request->getVar('grandtotal');

                $caltulateDiscont = (float) ($percentDiscount / 100) * $getGrandtotal;

                $discountMoney = (float)  $getGrandtotal - (float)$caltulateDiscont;

                $data['grandtotal'] = $discountMoney;

                $data['roundtrip_discount'] = $caltulateDiscont;

            } else {



                $data['grandtotal'] = $this->request->getVar('grandtotal');

                $data['roundtrip_discount'] = 0;

            }







            $data['pickstand'] = $this->request->getVar('pickstand');

            $data['dropstand'] = $this->request->getVar('dropstand');





            $data['paymethod'] = $this->paymethodModel->where('status', 1)->findAll();



            $data['module'] =    lang("Localize.ticket_booking");

            $data['title']  =    lang("Localize.ticket_list");



            $heading = lang("Localize.book") . ' ' . lang("Localize.ticket");

            $data['pageheading'] = $heading;



            $data['can_booking_discount'] = $this->canApplyBookingDiscount();

            $data['discount'] = $data['can_booking_discount'] ? 1 : 0;

            $data['coupon'] = $this->session->get('coupon');

            $data['rountripStatus'] = 0;

            $data['isrountrip'] = 0;

            $maxtime = $this->maxtimeModel->where('book_from', "admin")->first();

            $expirytime = $maxtime->maxtime ?? 5;

            // $expirytime = 1;

            $expires_at = get_egypt_time()->modify("+{$expirytime} minutes")->format('Y-m-d H:i:s');

            $data['expirytime_minutes'] = $expirytime;

            $data['expires_at'] = $expires_at;





            

            // $tempbooking = ['subtrip_id'   => $this->request->getVar('subtripId'),

            //                 'ticket_token' => 'backendd'.$this->request->getVar('subtripId'),

            //                 'seat_names'   => $this->request->getVar('seatnumbers'),

            //                 'journey_date' => $this->request->getVar('journeydate'),

            //                 'created_at'   => date('Y-m-d H:i:s'),

            //                 'expires_at'   => date('Y-m-d H:i:s', strtotime('+5 minutes'))];



            // $this->TemporaryBook->insert($tempbooking);



            echo view($this->Viewpath . '\ticket\booking', $data);

        }

    }



    public function create()

    {

        



        $rand = substr(str_shuffle('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 1, 8);

        $rand = "TB" . $rand;

        $maitripid = null;

        $subtripid = null;

        $piclocation = null;

        $droplocation = null;

        $vehicles_id = null;

        $backUserId = $this->session->get('user_id');

        $subTripId = $this->request->getVar('subtripId');

        $payment_detail_rocord = $this->request->getVar('paydetail');



        $subtrips = $this->subtripModel->select('trips.id as tripid,trips.*,fleets.*,schedules.*,vehicles.*,subtrips.id as subtripId,subtrips.*,trips.company_id as companyId')

            ->join('trips', 'trips.id = subtrips.trip_id')

            ->join('fleets', 'fleets.id = trips.fleet_id')

            ->join('schedules', 'schedules.id = trips.schedule_id')

            ->join('vehicles', 'vehicles.id = trips.vehicle_id',"left")

            ->where('subtrips.status', 1)

            ->where('subtrips.id', $subTripId)

            ->findAll();

        $companyId = $subtrips[0]->companyId??0;

        foreach ($subtrips as $key => $value) {

            $maitripid = $value->tripid;

            $subtripid = $value->id;

            $piclocation = $value->pick_location_id;

            $droplocation = $value->drop_location_id;

            $vehicles_id = $value->vehicle_id;

        }



        // get user info

        $login_mobile = $this->request->getVar('login_mobile');
        $login_email = $this->request->getVar('login_email');

        if ($staffMobileError = BookingPassengerSupport::staffMobileError($this->userModel, $login_mobile)) {
            return redirect()->back()->withInput()->with('fail', $staffMobileError);
        }

        // Boarding Date (what the user selected)
        $requestedJourneyDate = BookingInventorySupport::normalizeCalendarDate((string) $this->request->getVar('journeydate'));
        if (! BookingInventorySupport::canBookBackdatedTrips() && BookingInventorySupport::isBackdatedCalendarDate($requestedJourneyDate)) {
            return redirect()->back()->with('fail', 'Booking for backdated trips is not allowed for your role.');
        }
        
        $pickStandId = (int) $this->request->getVar('pickstand');

        $overnightRun = new \App\Libraries\OvernightTripRunSupport();
        $pickstandMessage = $overnightRun->validatePickStandForSeatInventory((int) $maitripid, (int) $subtripid, $pickStandId);
        if ($pickstandMessage !== null) {
            return redirect()->back()->with('fail', $pickstandMessage);
        }

        $serviceDateOverride = trim((string) ($this->request->getVar('bus_service_date') ?? ''));
        $bookingDates = BookingInventorySupport::resolveBookingStorageDates(
            (int) $maitripid,
            (int) $subtripid,
            $requestedJourneyDate,
            $pickStandId,
            $serviceDateOverride !== '' ? $serviceDateOverride : null
        );
        $busStartDate = $bookingDates['bus_start'];
        $journeyDate  = $bookingDates['boarding'];

        

        $first_name = $this->request->getVar('first_name');

                $fullName = $this->nameFilter($first_name);

                $first_name = $fullName['first_name'];

              //  print_r($fullName);die;

        if($login_mobile == ""){

            $demoemail = $first_name."123@gmail.com";

            $login_email = $demoemail;

        }

        

        $userid = $this->userCheck($login_email, $login_mobile);

        if (empty($userid)) {
            return redirect()->back()->withInput()->with('fail', 'Passenger account could not be created. Please check mobile and name.');
        }

        if ($roleError = BookingPassengerSupport::nonPassengerRoleError($this->userModel, (int) $userid)) {
            return redirect()->back()->withInput()->with('fail', $roleError);
        }

        $this->syncPassengerUserDetailsFromForm((int) $userid);

        $loginUserId = $this->session->get('user_id');

        $bookUserId = $this->session->get('role_id');



        // Get role info

        $roelinfo = $this->roleModel->find($bookUserId);

        $bookUserType = $roelinfo->name;



        $ticketbooking = array(

            "booking_id" => $rand,

            "trip_id" => $maitripid,

            "subtrip_id" => $subtripid,

            "passanger_id" => $userid,

            "pick_location_id" => $piclocation,

            "drop_location_id" => $droplocation,

            "pick_stand_id" => $this->request->getVar('pickstand'),

            "drop_stand_id" => $this->request->getVar('dropstand'),

            "price" => $this->request->getVar('totalprice'),

            "discount" => $this->sanitizeBookingDiscount($this->request->getVar('discount')),

            "totaltax" => $this->request->getVar('tax'),

            "paidamount" => $this->request->getVar('grandtotal'),

            "adult" => $this->request->getVar('aseat'),

            "chield" => $this->request->getVar('cseat'),

            "special" => $this->request->getVar('spseat'),

            "vip" => $this->request->getVar('vpseat')??"",

            "refund" => 0,

            "bookby_user_id" =>  $loginUserId,

            "bookby_user_type" => $bookUserType,

            "journeydata" => $journeyDate,

            "pay_type_id" => $this->request->getVar('pay_method'),

            "payment_status" => $this->request->getVar('payment_status'),

            "payment_detail" => $this->request->getVar('paydetail'),

            "note" => $this->request->getVar('address')??"",

            "vehicle_id" => $vehicles_id,

            "cancel_status" => 0,

            "offerer" => $this->request->getVar('offerer'),

            "seatnumber" => $this->request->getVar('seatnumbers'),

            "totalseat" => $this->request->getVar('totalseat'),

            "roundtrip_discount" => 0,

        );

        $this->db->transStart();

        BookingInventorySupport::lockTripRowForUpdate($this->db, (int) $maitripid);

        $ticket_token = $this->request->getVar('ticket_token') ?? $this->request->getCookie('_chkseats_token') ?? session('ticket_token');
        $allBookedSeats = $this->isAlreadybookedsubtrip(
            $piclocation,
            $droplocation,
            $subtripid,
            $busStartDate,
            $maitripid,
            $ticket_token,
            $requestedJourneyDate,
            (int) $this->request->getVar('pickstand')
        );

        $requestedSeats = array_map('trim', array_filter(explode(',', (string)$this->request->getVar('seatnumbers'))));
        $intersection = array_intersect($requestedSeats, $allBookedSeats);

        if (!empty($intersection)) {
            $this->db->transRollback();
            return redirect()->back()->withInput()->with('fail', admin_bilingual_lang('Localize.seat_already_booked_segment', ['seats' => implode(',', $intersection)]));
        }

        if ($this->validation->run($ticketbooking, 'ticket')) {

            $paymentStatus = $this->request->getVar('payment_status');



            if ($paymentStatus == "unpaid") {

                $paidamount = 0;

            }



            if ($paymentStatus == "paid") {

                $paidamount = $this->request->getVar('grandtotal');

            }



            if ($paymentStatus == "partial") {

                $paidamount = $this->request->getVar('partialpay');

            }

            $userRole = $this->session->get('role_id');



            if(($userRole == 2)){



                $agetnDetail =  $this->agentModel->where('user_id', $this->session->get('user_id'))->first();



                $walletAmount = $this->agentPayment->select('SUM(payagents.amount) as total_amount')

                ->join('agents', 'agents.id = payagents.agent_id', 'left')

                ->join('companies', 'companies.id = payagents.company_id', 'left')

                ->where('payagents.agent_id', $agetnDetail->id)

                ->where('companies.id ',$companyId)

                ->where('payagents.status',1)

                ->groupBy('companies.name')

                ->first();

                if (!is_object($walletAmount) || agent_wallet_insufficient_for_booking($walletAmount->total_amount ?? null, $paidamount)){

                    $errmsg = admin_bilingual_lang('Localize.insufficient_balance');

                    $seatNumbers = $this->request->getVar('seatnumbers');

                    $this->db->transRollback();
                    $this->deleteHoldSeat($seatNumbers,$subtripid);

                    return redirect()->route('allbookinglist-ticket')->with("fail", $errmsg);

                }



            }

            



            $partialPaid = array(

                "booking_id" => $rand,

                "trip_id" => $maitripid,

                "subtrip_id" => $subtripid,

                "passanger_id" => $userid,

                "paidamount" => $paidamount,

                "pay_type_id" => $this->request->getVar('pay_method'),

            );

            $paidpartial = array(

                "booking_id" => $rand,

                "trip_id" => $maitripid,

                "subtrip_id" => $subtripid,

                "passanger_id" => $userid,

                "paidamount" => $paidamount,

                "pay_type_id" => $this->request->getVar('pay_method'),

                "payment_detail" => $this->request->getVar('paydetail'),

            );



            $this->ticketModel->insert($ticketbooking);
            $ticketInsertId = $this->ticketModel->getInsertID();
            if (empty($ticketInsertId)) {
                $this->db->transRollback();
                $this->deleteHoldSeat($this->request->getVar('seatnumbers'), $subtripid);

                return redirect()->back()->withInput()->with('fail', 'Ticket could not be saved. Please try again.');
            }

            $pick_stand_id = $this->request->getVar('pickstand');

            $drop_stand_id = $this->request->getVar('dropstand');

            BookingInventorySupport::logBookingDateContext('admin_create_journeylist', [
                'trip_id' => (int) $maitripid,
                'subtrip_id' => (int) $subtripid,
                'search_date' => $requestedJourneyDate,
                'pickstand' => (int) $pick_stand_id,
                'service_date' => $busStartDate,
                'boarding_date' => $journeyDate,
                'stored_ticket_date' => $journeyDate,
                'stored_manifest_date' => $busStartDate,
                'booking_id' => $rand,
            ]);

            $this->journeylist($rand, $userid, $maitripid, $subtripid, $piclocation, $droplocation, $pick_stand_id, $drop_stand_id, $busStartDate);

            if ($this->validation->run($partialPaid, 'partialpay')) {



                $this->partialpaidModel->insert($paidpartial);



                



                if (($userRole == 2) && ($paymentStatus != "unpaid")) {

                    $totalprice =  $paidamount;

                    $type = "income";

                    $message = "For Ticket Booking";



                    $agentIncome = agentIncomeCommission($backUserId, $totalprice, $rand, $subtripid, $userid, $message, $companyId);



                    $agentTotalIncome =   agentTotal($backUserId, $totalprice, $rand, $type, $payment_detail_rocord);



                    // if( $this->request->getVar('pay_method')== 1){

                        $agetnDetail =  $this->agentModel->where('user_id', $this->session->get('user_id'))->first();

                        $agentIncome = agentExpenseTicketCharge($agetnDetail->id, $totalprice, $rand, $subtripid, $userid, $message, $companyId);

                    // }

                }

            }



            if ($paymentStatus != "unpaid") {

                $paymethod_id = $this->request->getVar('pay_method');

                $payDetail = $this->request->getVar('paydetail');

                $type = "income";

                $detail = "Ticket Booking (" . $rand . ") ";

                accoutTranjection($type, $detail, $paidamount, $backUserId, $rand, "booking");

                paymethodTeanjection($rand, $paymethod_id, $paidamount, $payDetail, $maitripid, $subtripid, $backUserId);



                $couponcode = $this->request->getVar('offerer');



                if (!empty($couponcode)) {

                    $validDetail = $this->couponModel->where('code', $couponcode)->findAll();





                    $coupondetail = array(

                        "code" => $couponcode,

                        "coupon_id" => $validDetail[0]->id,

                        "booking_id" => $rand,

                        "subtrip_id" => $subtripid,

                        "amount" => $validDetail[0]->discount,



                    );



                    $this->coupondiscountModel->insert($coupondetail);

                }

            }



            $this->db->transComplete();

            $this->createTicketNotifications($rand, $userid, $maitripid, $subtripid, $journeyDate);

            try {
                $ticketmailLibrary = new Ticketmail();
                $emaildata = $ticketmailLibrary->getticketEmailData($rand);
                if (! empty($emaildata['ticket'])) {
                    sendTicket($login_email, $emaildata);
                }
            } catch (\Throwable $e) {
                log_message('error', 'Ticket email failed for {booking}: {message}', [
                    'booking' => $rand,
                    'message' => $e->getMessage(),
                ]);
            }

            return redirect()->route('allbookinglist-ticket')->with("success", "Ticket created successfully");

        }

        $this->db->transRollback();

        return redirect()->route('new-ticket')->with("fail", $this->validation->listErrors());

    }



    /**
     * Ensure passenger profile exists so ticket list and invoice email can resolve the name.
     */
    private function syncPassengerUserDetailsFromForm(int $userId): void
    {
        $profile = [
            'last_name' => $this->request->getVar('last_name'),
            'id_type' => $this->request->getVar('id_type') ?: null,
            'country_id' => $this->request->getVar('country_id') ?: null,
            'id_number' => $this->request->getVar('id_number') ?: null,
            'address' => $this->request->getVar('address') ?: null,
            'city' => $this->request->getVar('city') ?: null,
            'zip_code' => $this->request->getVar('zip_code') ?: null,
            'first_name' => '',
        ];

        $firstNameRaw = $this->request->getVar('first_name');
        if ($firstNameRaw !== null && trim((string) $firstNameRaw) !== '') {
            $fullName = $this->nameFilter($firstNameRaw);
            $profile['first_name'] = $fullName['first_name'];
        }

        BookingPassengerSupport::ensurePassengerUserDetails($this->userDetailModel, $userId, $profile);
    }

    public function userCheck($login_email, $login_mobile)
    {
        $userid = null;

        // Search including soft-deleted users
        if ($login_email != "") {
            $evalue = $this->userModel->withDeleted()->where('login_email', $login_email)->findAll();
        } else {
            $evalue = "";
        }

        if ($login_mobile != "") {
            $mvalue = $this->userModel->withDeleted()->where('login_mobile', $login_mobile)->findAll();
        } else {
            $mvalue = "";
        }

        if (!empty($evalue) || !empty($mvalue)) {
            if ($evalue) {
                foreach ($evalue as $key => $mobilevalue) {
                    $userid = $mobilevalue->id;
                }
            }
            if ($mvalue) {
                foreach ($mvalue as $key => $emailvalue) {
                    $userid = $emailvalue->id;
                }
            }

            // REACTIVATION LOGIC: If a deleted user is found, restore them
            $existingUser = $this->userModel->withDeleted()->find($userid);
            if (!empty($existingUser->deleted_at)) {
                // Restore the user and their details
                $this->userModel->update($userid, ['deleted_at' => null, 'status' => 1]);
                $this->userDetailModel->withDeleted()->where('user_id', $userid)->set('deleted_at', null)->update();
            }

            return $userid;
        } else {

            $status = 1;

            $role_id = 3;

            $slug = bin2hex(random_bytes(5));

            $password = $confirm = "123456";

            if($login_email == ""){

                $login_email = null;

            }else{

                $login_email = $login_email;

            }





            $userData = array(

                "login_email" => $login_email,

                "login_mobile" => $login_mobile,

                "password" => $password,

                "confirm" => $confirm,

                "slug" => $slug,

                "role_id" => $role_id,

                "status" => $status,

            );



            if ($this->validation->run($userData, 'user')) {

                $this->db->transStart();



                $userData['password'] = password_hash($password, PASSWORD_DEFAULT);

                $userid = $this->userModel->insert($userData);

                $first_name = $this->request->getVar('first_name');

                $fullName = $this->nameFilter($first_name);

                $first_name = $fullName['first_name'];

                $last_name = $fullName['last_name'];

                $validdata = array(

                    "user_id" => $userid,

                    "first_name" => $first_name,

                    "last_name" => $this->request->getVar('last_name'),

                    "id_type" => $this->request->getVar('id_type') ?: null,

                    "id_number" => $this->request->getVar('id_number') ?: null,

                    "country_id" => $this->request->getVar('country_id')?: null,

                );



                if ($this->validation->run($validdata, 'userDetail')) {

                    $data = array(

                        "user_id" => $userid,

                        "first_name" => $first_name,

                        "last_name" => $last_name,

                        "id_type" => $this->request->getVar('id_type') ?: null,

                        "country_id" => $this->request->getVar('country_id')?: null,

                        "id_number" => $this->request->getVar('id_number') ?: null,

                        "address" => $this->request->getVar('address')?: null,

                        "city" => $this->request->getVar('city')?: null,

                        "zip_code" => $this->request->getVar('zip_code')?: null

                    );



                    $this->userDetailModel->insert($data);



                    $this->db->transComplete();

                }

            }



            return $userid;

        }

    }

    

     function nameFilterold($full_name){

        // Split the full name by spaces

        $name_parts = explode(" ", $full_name);

        

        // Initialize variables for first name, middle name(s), and last name

        $first_name = ""; // Initialize first name as an empty string

        $middle_name = ""; // Initialize middle name as an empty string

        $last_name = ""; // Initialize last name as an empty string

        

        // Extract the last name (always the last part)

        $last_name = array_pop($name_parts);

    

        // If there are remaining parts, set the first name and middle name(s)

        if (!empty($name_parts)) {

            // First name is the first part

            $first_name = array_shift($name_parts);

            // If there are remaining parts, they are middle name(s)

            if (!empty($name_parts)) {

                // Concatenate the remaining parts as middle name(s)

                $middle_name = implode(" ", $name_parts);

            }

        }

        

        // Prepare the data array

        $data['first_name'] = $first_name;

        $data['last_name'] = $last_name;

        

        return $data;

    }

    function nameFilter($full_name) {

        // Trim and split the name at the first space

        $full_name = trim($full_name);

        $spacePos = strpos($full_name, ' ');



        if ($spacePos === false) {

            // No space found — whole name is the first name

            $first_name = $full_name;

            $last_name = '';

        } else {

            $first_name = substr($full_name, 0, $spacePos);

            $last_name = substr($full_name, $spacePos + 1);

        }



        return [

            'first_name' => $first_name,

            'last_name' => $last_name

        ];

    }





    public function journeylist($rand, $userid, $maitripid, $subtripid, $piclocation, $droplocation, $pick_stand_id, $drop_stand_id, $journeydate = "")

    {

        if ($journeydate === "") {
            $pickStandForDates = (int) $pick_stand_id;
            $searchDate        = BookingInventorySupport::normalizeCalendarDate((string) $this->request->getVar('journeydate'));
            $dates             = BookingInventorySupport::resolveSearchDepartureDates(
                (int) $maitripid,
                (int) $subtripid,
                $searchDate,
                $pickStandForDates
            );
            $journeydate = $dates['bus_start'];
        } else {
            // Caller already passed normalized bus run day from booking flow.
            $journeydate = BookingInventorySupport::normalizeCalendarDate((string) $journeydate);
        }

        $joruneylistid = null;



        $mainpassanger = array(

            "booking_id" => $rand,

            "trip_id" => $maitripid,

            "subtrip_id" => $subtripid,

            "pick_location_id" => $piclocation,

            "drop_location_id" => $droplocation,

            "pick_stand_id" => $pick_stand_id,

            "drop_stand_id" => $drop_stand_id,

            "first_name" => $this->request->getVar('first_name'),

            "last_name" => $this->request->getVar('last_name'),

            "phone" => $this->request->getVar('login_mobile'),

            "journeydate" => $journeydate,

            "id_number" => $this->request->getVar('id_number'),

        );



        if ($this->validation->run($mainpassanger, 'journeylist')) {

            $joruneylistid = $this->journeylistModel->insert($mainpassanger);

        }



        $newPassangerFName = $this->request->getVar('first_name_new');

        $newPassangerLName = $this->request->getVar('last_name_new');

        $newPassangerMobile = $this->request->getVar('login_mobile_new');

        $newPassangerNidNumber = $this->request->getVar('id_number_new');



        if (!empty($newPassangerFName)) {

            foreach ($newPassangerFName as $nkey => $newpassanger) {

                $newpassangerlist[$nkey] = array(



                    "booking_id" => $rand,

                    "trip_id" => $maitripid,

                    "subtrip_id" => $subtripid,

                    "pick_location_id" => $piclocation,

                    "drop_location_id" => $droplocation,

                    "pick_stand_id" => $pick_stand_id,

                    "drop_stand_id" => $drop_stand_id,

                    "first_name" => $newPassangerFName[$nkey],

                    "last_name" => $newPassangerLName[$nkey],

                    "phone" => $newPassangerMobile[$nkey],

                    "journeydate" => $journeydate,

                    "id_number" => $newPassangerNidNumber[$nkey],



                );

            }







            $this->journeylistModel->insertBatch($newpassangerlist);

        }





        return   $joruneylistid;

    }



    public function allbookinglist()

    {

        $rolepermissionLibrary = new Rolepermission();

        $refund_action = "refund_list";

        $cancel_action = "cancel_list";



        $data['refund_create'] = $rolepermissionLibrary->create($refund_action);

        $data['cancel_create'] = $rolepermissionLibrary->create($cancel_action);
        $ridList = (int) session()->get('role_id');
        $data['cancel_create_online'] = ($ridList === 1) || $rolepermissionLibrary->create('cancel_online_ticket');



        $pickdrops =  $this->picdropModel

            ->select('stands.id as standId,stands.*,pickdrops.*, stands.name as stand_name')

            ->join('stands', 'stands.id = pickdrops.stand_id')

            ->withDeleted()

            ->findAll();



        // print_r($pickdrops);

        // dd("asdasd");



        // use pagination to avoid loading all tickets at once
        $perPage = 50; // tickets per page
        $data['ticket'] = $this->filterBooking(true, $perPage);
        $data['pager'] = $this->ticketModel->pager;

        $data['location'] =  $this->locationModel->withDeleted()->findAll();

        $data['pickdropstand'] =  $pickdrops;

        $data['paymethod'] = $this->paymethodModel->where('status', 1)->findAll();



        // capture search term (server-side filter across all tickets)
        $search = $this->request->getGet('search');
        $data['search'] = $search;

        $data['module'] =    lang("Localize.ticket_booking");

        $data['title']  =    lang("Localize.ticket_list");

        $data['pageheading'] = lang("Localize.ticket_list");



        return view($this->Viewpath . '\ticket\bookinglist', $data);

    }



    public function filterBooking(bool $usePagination = false, int $perPage = 50)

    {

        $userRole = $this->session->get('role_id');

        $userId = $this->session->get('user_id');

        // print_r($this->session->get());

        // exit;

        if (!in_array($userRole,[1,2,7])) {

            //not agent company and super admin

            // user role is agent or employee

            // list only user booking lists

            $rolepermissionLibrary = new Rolepermission();

            if (!$rolepermissionLibrary->create('all_tickets')) {

                $this->ticketModel->where('bookby_user_id', $userId);

            }else{

                $this->ticketModel->where('trips.company_id', session()->get('company_id'));

            }

        }else{

            if(in_array($userRole,[7])){

              $this->ticketModel->where('trips.company_id', session()->get('company_id'));

            }

            if(in_array($userRole,[2])){

              $this->ticketModel->whereIn('trips.company_id', explode(",", session()->get('company_id')))->where('bookby_user_id', $userId);

            }

        }



        $this->ticketModel->where('cancel_status', 0);

        $this->ticketModel->where('refund', 0);

        // $this->ticketModel->where('tickets.journeydata >=', '2025-08-21 00:00:00');

        // global search filter (if provided)
        $search = $this->request->getGet('search');
        if (!empty($search)) {
            $this->ticketModel
                ->groupStart()
                    ->like('tickets.booking_id', $search)
                    ->orLike('companies.name', $search)
                    ->orLike('user_details.first_name', $search)
                    ->orLike('user_details.last_name', $search)
                    ->orLike('tickets.payment_status', $search)
                ->groupEnd();
        }

        $this->ticketModel->select('tickets.*,CONCAT(COALESCE(NULLIF(TRIM(user_details.first_name), ""), passenger_agents.first_name, ""), " ", COALESCE(NULLIF(TRIM(user_details.last_name), ""), passenger_agents.last_name, "")) as passengerName, CONCAT(COALESCE(booker.first_name, booker_agents.first_name,booker_agents_emp.first_name), " ", COALESCE(booker.last_name, booker_agents.last_name, booker_agents_emp.last_name)) AS booked_by, companies.name as company_name');

        $this->ticketModel->join("user_details","user_details.user_id = tickets.passanger_id", "left");

        $this->ticketModel->join("agents AS passenger_agents","passenger_agents.user_id = tickets.passanger_id", "left");

        $this->ticketModel->join("user_details AS booker","booker.user_id = tickets.bookby_user_id","left");

        $this->ticketModel->join("agents AS booker_agents","booker_agents.user_id = tickets.bookby_user_id","left");

        $this->ticketModel->join("users","users.id = tickets.bookby_user_id","left");

        $this->ticketModel->join("employees AS booker_agents_emp","booker_agents_emp.email = users.login_email","left");

        $this->ticketModel->join("trips","trips.id = tickets.trip_id","left");

        $this->ticketModel->join("companies","trips.company_id = companies.id","left");

        $this->ticketModel->orderBy('id', 'DESC');

        if ($usePagination) {
            // Use pagination instead of loading all tickets into memory.
            // This keeps the ticket list fast even with a large number of records.
            return $this->ticketModel->withDeleted()->paginate($perPage);
        }

        return $this->ticketModel->withDeleted()->findAll();

    }

    /**
     * Export all tickets (optionally filtered by search term) as CSV.
     */
    public function exportAllTickets()
    {
        // reuse the same filters as the list, but without pagination
        $tickets = $this->filterBooking(false);

        $fp = fopen('php://temp', 'r+');
        fputcsv($fp, [
            '#',
            'Booking ID',
            'Company',
            'Pick Location',
            'Drop Location',
            'Seat Number',
            'Passenger Name',
            'Payment Status',
            'Journey Date',
            'Trip ID',
            'Booked By',
        ]);

        $i = 1;
        $journeyDateHelper = new \App\Libraries\TripJourneyDateHelper();
        foreach ($tickets as $value) {
            $journeyDate = $journeyDateHelper->getPassengerBoardingDateForDisplay(
                (int) ($value->trip_id ?? 0),
                (int) ($value->subtrip_id ?? 0),
                (string) ($value->journeydata ?? ''),
                (int) ($value->pick_stand_id ?? 0)
            );
            $bookedBy = isset($value->bookby_user_type)
                ? (($value->bookby_user_type == 'passanger') ? 'online' : $value->bookby_user_type) . '(' . ($value->booked_by ?? '') . ')'
                : '';

            fputcsv($fp, [
                $i++,
                $value->booking_id,
                $value->company_name,
                '', // pick location name (left blank here to avoid heavy joins again)
                '', // drop location name
                $value->seatnumber,
                $value->passengerName,
                $value->payment_status,
                $journeyDate,
                $value->trip_id,
                $bookedBy,
            ]);
        }

        rewind($fp);
        $csv = stream_get_contents($fp);
        fclose($fp);

        return $this->response
            ->setHeader('Content-Type', 'text/csv')
            ->setHeader('Content-Disposition', 'attachment; filename="tickets_' . date('Ymd_His') . '.csv"')
            ->setBody($csv);
    }



    public function agentCommission($totalprice)

    {

        $userId = $this->session->get('user_id');

        $agetnDetail =  $this->agentModel->where('user_id', $userId)->first();

        (float)$commission = (float)(($totalprice) * ($agetnDetail->commission / 100));

        return $commission;

    }





    public function roundfindtrip()

    {

        $websetting = $this->websettingModel->first();



        $pick_location_id = $this->session->get('drop_location_id');

        $drop_location_id = $this->session->get('pick_location_id');

        $filterjourneydate = $this->session->get('filterreturndate');



        // build journey date

        $journeyDate = BookingInventorySupport::normalizeCalendarDate((string) $filterjourneydate);
        if (! BookingInventorySupport::canBookBackdatedTrips() && BookingInventorySupport::isBackdatedCalendarDate($journeyDate)) {
            return redirect()->back()->with('fail', 'Searching for backdated trips is not allowed for your role.');
        }

        $journeyDayOfWeek = date('N', strtotime($filterjourneydate));



        $fleet_id = $this->session->get('fleet_id');



        $monthcomparedate = get_egypt_time('+1 month')->format('Y-m-d');



        if ($filterjourneydate > $monthcomparedate) {

            return redirect()->route('new-ticket')->with("fail", "No advance booking for this day");

        }

        $company_id = session()->get('company_id') ?? 0;

        if(($this->request->getVar('company_id') || $this->request->getVar('company_id') == 0) && session()->get('role_id') == "1"){

            $company_id = $this->request->getVar('company_id') ?? 0;

        } else {

            if(session('search_company_id')){

                $company_id = session('search_company_id');

            }

        }



        $data = array('trips' => [], 'in_holiday' => [], 'to_open' => [], 'inactive' => [], 'you_may_like' => []);

        $data['pick_location_id'] = $pick_location_id;

        $data['drop_location_id'] = $drop_location_id;

        $data['filterjourneydate'] = $filterjourneydate;

        $data['filterreturndate'] = "";

        $data['fleet_id'] = $fleet_id;

        $data['company_id'] = $company_id;



        $data['filterpath'] = $this->Viewpath;

        $data['location'] = $this->locationModel->findAll();

        $data['fleet_type'] = $this->fleetTypeModel->findAll();

        $data['companies'] = $this->CompanyModel->findAll();





        if (strpos($company_id, ',')) { 

            $company_ids = explode(',', $company_id);

        } else {

            $company_ids = [$company_id];

        }



        $allSubTrips = $this->getAllTrip($pick_location_id, $drop_location_id, $fleet_id, $company_ids);



        foreach ($allSubTrips as $subTrip) {
            \App\Libraries\TripDisplaySupport::applyParentTripDistanceAndHours($subTrip);

            if ($subTrip->status == 0) {

                // subtrip is inactive

                $data['inactive'][] = $subTrip;

                continue;

            }



            if (strtotime($journeyDate) < strtotime($subTrip->startdate)) {

                // subtrip yet not begin

                $data['to_open'][] = $subTrip;

                continue;

            }
            
            $cleanedoffdates = preg_replace('/\s+/', '', $subTrip->offdates);

            if (in_array($journeyDate,explode(",", $cleanedoffdates))) {

                // subtrip is off today

                $data['in_holiday'][] = $subTrip;

                continue;

            }
            
            if ($this->isDateInRange($journeyDate, $subTrip->startdateinrange, $subTrip->enddateinrange)) {

                // subtrip is not in range

                $data['in_holiday'][] = $subTrip;

                continue;

            }
            
            $offdateranges = json_decode($subTrip->offdateranges, true);

            $isInAnyRange = false;

            if (is_array($offdateranges) && !empty($offdateranges)) {

                foreach ($offdateranges as $range) {

                    $start = $range['start'] ?? null;

                    $end = $range['end'] ?? null;



                    if ($start && $end && $this->isDateInRange($journeyDate, $start, $end)) {

                        $isInAnyRange = true;

                        break;

                    }

                }

            }



            if ($isInAnyRange) {

                $data['in_holiday'][] = $subTrip;

                continue;

            }
            if (!$this->isDateInRange($journeyDate, $subTrip->repetedstartdate, $subTrip->repetedenddate)) {

                if($journeyDate != date('Y-m-d',strtotime($subTrip->startdate))){

                    //start date and not between start and edn date

                    $data['in_holiday'][] = $subTrip;

                    continue;    

                }

                

            }



            $today = new \DateTime(date('Y-m-d',strtotime($subTrip->repetedstartdate)));



            $journeyDateObj = new \DateTime(date('Y-m-d', strtotime($journeyDate)));

            $interval = $today->diff($journeyDateObj);

            $bookingToday = new \DateTime(egypt_today());
            $bookingWindowInterval = $bookingToday->diff($journeyDateObj);

            if (
                $subTrip->maxdayforbooking > 0
                && $journeyDateObj >= $bookingToday
                && ($subTrip->maxdayforbooking < $bookingWindowInterval->days)
            ) {

                //not allowed to book tickets

                $data['in_holiday'][] = $subTrip;

                continue;

            }



            if ($this->isDateInRange($journeyDate, $subTrip->repetedstartdate, $subTrip->repetedenddate)) {

                if($journeyDate != date('Y-m-d',strtotime($subTrip->startdate))){

                    if($subTrip->repeted_after > 0 && $interval->days > 0){

                        if($interval->days%($subTrip->repeted_after+1) > 0){

                            //start date and not between start and edn date

                            $data['in_holiday'][] = $subTrip;

                            continue;

                        }

                    }

                    

                }

                

            }


            if (@in_array($journeyDayOfWeek, explode(',', $subTrip->weekend))) {

                // subtrip is on holiday

                $data['in_holiday'][] = $subTrip;

                continue;

            }



            if (($subTrip->pick_location_id != $pick_location_id) || ($subTrip->drop_location_id != $drop_location_id)) {

                // subtrip is related to selected locations

                $data['you_may_like'][] = $subTrip;

                continue;

            }

            

            $subtripInfo = $this->subtripModel->find($subTrip->id);

            $mainTripId = (int) $subtripInfo->trip_id;
            $firstPickdrop = $this->picdropModel
                ->where('trip_id', $subtripInfo->id)
                ->where('type', 1)
                ->orderBy('id', 'ASC')
                ->first();
            if (! $firstPickdrop) {
                $mainSubtrip = $this->subtripModel->where('trip_id', $mainTripId)->where('type', 'main')->first();
                if ($mainSubtrip) {
                    $firstPickdrop = $this->picdropModel
                        ->where('trip_id', $mainSubtrip->id)
                        ->where('type', 1)
                        ->orderBy('id', 'ASC')
                        ->first();
                }
            }
            $pickStandForDate = $firstPickdrop ? (int) $firstPickdrop->id : 0;
            $busStartForInventory = BookingInventorySupport::busRunForTripSearch($filterjourneydate);
            $stoppageString = BookingInventorySupport::resolveTripStoppageForOverlap($mainTripId, (int) $subtripInfo->id);

            $results = $this->getDynamicPickupDropCombinations(
                $subtripInfo->pick_location_id,
                $subtripInfo->drop_location_id,
                $stoppageString,
                $busStartForInventory,
                $mainTripId,
                (int) $subtripInfo->id,
                null,
                $pickStandForDate
            );
            $bookseat = array_column($results, 'seatnumber');



            $separatedSeats = [];

            foreach ($bookseat as $seats) {

                // Use explode to separate the comma-separated seat numbers

                $separatedSeats = array_merge($separatedSeats, explode(',', $seats));

            }

            $subTrip->bookseatCount = is_array($separatedSeats)?count($separatedSeats):0;

            $pickdrop = $this->picdropModel->select('pickdrops.id as pickdropid,pickdrops.*,stands.*, stands.name as stand_name')

                ->join('stands', 'stands.id = pickdrops.stand_id')

                ->where('trip_id', $subtripInfo->id)

                ->findAll();

            if(empty($pickdrop)){

                $pickdrop = $this->picdropModel->select('pickdrops.id as pickdropid,pickdrops.*,stands.*, stands.name as stand_name')

                    ->join('stands', 'stands.id = pickdrops.stand_id')

                    ->where('trip_id', $subtripInfo->trip_id)

                    ->findAll();

            }

            // print_r($pickdrop);

            $pickup_time = null;

            $drop_time = null;



            // foreach ($pickdrop as $item) {

            //     if ($item->type == 1) {

            //         $pickup_time = $item->time;

            //     } elseif ($item->type == 0) {

            //         $drop_time = $item->time;

            //     }

            // }

            $pickup_time = null;

            $drop_time = null;



            foreach ($pickdrop as $item) {

                if ($item->type == 1) {

                    // Smallest pickup time

                    if (is_null($pickup_time) || strtotime($item->time) < strtotime($pickup_time)) {

                        $pickup_time = $item->time;

                    }

                } elseif ($item->type == 0) {

                    // Largest drop time

                    if (is_null($drop_time) || strtotime($item->time) > strtotime($drop_time)) {

                        $drop_time = $item->time;

                    }

                }

            }





            $subTrip->pickup_time = $pickup_time;
            $subTrip->drop_time = $drop_time;

            $data['trips'][] = $subTrip;

        }



        $data['taxtype'] = $websetting->tax_type;

        $data['rountripStatus'] = 1;



        $data['module'] =    lang("Localize.ticket_booking");

        $data['title']  =    lang("Localize.book_ticket");



        $heading = lang("Localize.book") . ' ' . lang("Localize.ticket");

        $data['pageheading'] = $heading;

        $data['triptype'] = lang("Localize.round") . ' ' . lang("Localize.trip");

        $data['isrountrip'] = 1;

        $data['totalseat'] = $this->session->get('totalseat');



        $isroundtrip['isrountrip'] = 1;



        $this->session->set($isroundtrip);



        return view($this->Viewpath . '\ticket\index', $data);

    }





    public function roundcreate()

    {



        $ticketmailLibrary = new Ticketmail();



        //single Trip Ticket Booking

        $rand = substr(str_shuffle('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 1, 8);

        $rand = "TB" . $rand;

        $maitripid = null;

        $subtripid = null;

        $piclocation = null;

        $droplocation = null;

        $vehicles_id = null;

        $backUserId = $this->session->get('user_id');

        $subTripId = $this->session->get('subtripId');

        $payment_detail_rocord = $this->request->getVar('paydetail');



        $discount = $this->sanitizeBookingDiscount($this->request->getVar('discount'));

        if ($discount > 0) {
            $getDiscount = (float) $discount / 2;
        } else {
            $getDiscount = 0;
        }

        $subtrips = $this->subtripModel->select('trips.id as tripid,trips.*,fleets.*,schedules.*,vehicles.*,subtrips.id as subtripId,subtrips.*,trips.company_id as companyId')

            ->join('trips', 'trips.id = subtrips.trip_id')

            ->join('fleets', 'fleets.id = trips.fleet_id')

            ->join('schedules', 'schedules.id = trips.schedule_id')

            ->join('vehicles', 'vehicles.id = trips.vehicle_id', "left")

            ->where('subtrips.status', 1)

            ->where('subtrips.id', $subTripId)

            ->findAll();

        $companyId = $subtrips[0]->companyId??0;


        foreach ($subtrips as $key => $value) {

            $maitripid = $value->tripid;

            $subtripid = $value->id;

            $piclocation = $value->pick_location_id;

            $droplocation = $value->drop_location_id;

            $vehicles_id = $value->vehicle_id;

        }





        $login_email = $this->request->getVar('login_email');

        $login_mobile = $this->request->getVar('login_mobile');

        if ($staffMobileError = BookingPassengerSupport::staffMobileError($this->userModel, $login_mobile)) {
            return redirect()->back()->withInput()->with('fail', $staffMobileError);
        }

        $userid = $this->userCheck($login_email, $login_mobile);

        if (empty($userid)) {
            return redirect()->back()->withInput()->with('fail', 'Passenger account could not be created. Please check mobile and name.');
        }

        if ($roleError = BookingPassengerSupport::nonPassengerRoleError($this->userModel, (int) $userid)) {
            return redirect()->back()->withInput()->with('fail', $roleError);
        }

        $this->syncPassengerUserDetailsFromForm((int) $userid);

        $loginUserId = $this->session->get('user_id');



        $bookUserId = $this->session->get('role_id');



        $roelinfo = $this->roleModel->find($bookUserId);



        $bookUserType = $roelinfo->name;



        $pay_method_id = $this->request->getVar('pay_method');



        $payment_status = $this->request->getVar('payment_status');

        $payment_detail = $this->request->getVar('paydetail');

        $requestedJourneyDate = BookingInventorySupport::normalizeCalendarDate((string) $this->session->get('journeydate'));
        $journeyDateHelper    = new TripJourneyDateHelper();
        $outwardPickStand     = (int) $this->session->get('pickstand');
        $outwardBusStartDate  = $requestedJourneyDate;
        $outwardTicketDate    = BookingInventorySupport::ticketBoardingDateForStorage(
            (int) $maitripid,
            (int) $subtripid,
            $outwardBusStartDate,
            $outwardPickStand
        );



        $ticketbooking = array(

            "booking_id" => $rand,

            "trip_id" => $maitripid,

            "subtrip_id" => $subtripid,

            "passanger_id" => $userid,

            "pick_location_id" => $piclocation,

            "drop_location_id" => $droplocation,

            "pick_stand_id" => $this->session->get('pickstand'),

            "drop_stand_id" => $this->session->get('dropstand'),

            "price" => $this->session->get('totalprice'),

            "discount" =>  $getDiscount,

            "roundtrip_discount" =>  $this->session->get('roundtrip_discount'),

            "totaltax" => $this->session->get('tax'),

            "paidamount" => $this->session->get('grandtotal'),

            "adult" => $this->session->get('aseat'),

            "chield" => $this->session->get('cseat'),

            "special" => $this->session->get('spseat'),

            "vip" => $this->session->get('vpseat')??"",

            "refund" => 0,

            "bookby_user_id" =>  $loginUserId,

            "bookby_user_type" => $bookUserType,

            "journeydata" => $outwardTicketDate,

            "pay_type_id" =>  $pay_method_id,

            // "payment_status" => $payment_status,

            "payment_detail" => $payment_detail,

            "vehicle_id" => $vehicles_id,

            "cancel_status" => 0,



            "offerer" => $this->session->get('offerer'),

            // "offerer" => 0,

            "seatnumber" => $this->session->get('seatnumbers'),

            "totalseat" => $this->session->get('totalseat'),



        );



        $validTicketbooking = array(



            "booking_id" => $rand,

            "trip_id" => $maitripid,

            "subtrip_id" => $subtripid,

            "passanger_id" => $userid,



            "pick_location_id" => $piclocation,

            "drop_location_id" => $droplocation,

            "pick_stand_id" => $this->session->get('pickstand'),

            "drop_stand_id" => $this->session->get('dropstand'),



            "price" => $this->request->getVar('totalprice'),

            "paidamount" => $this->request->getVar('grandtotal'),

            "seatnumber" => $this->request->getVar('seatnumbers'),

            "totalseat" => $this->session->get('totalprice'),

            "bookby_user_id" => 1,

            "journeydata" => $outwardTicketDate,

            "pay_type_id" => $pay_method_id,

            "payment_status" => $payment_status,

            "vehicle_id" => $vehicles_id,



        );





        (new \App\Libraries\TemporaryBookHoldService())->cleanupExpiredHolds();

        $ticket_token = $this->request->getVar('ticket_token')
            ?? $this->request->getCookie('_chkseats_token')
            ?? session('ticket_token');

        $this->db->transStart();

        // Lock the trip for this specific date to prevent concurrent bookings for overlapping segments
        $this->db->table('trips')->where('id', $maitripid)->set('updated_at', egypt_now_sql())->update();

        // Check availability for outward leg
        $checksubtripbookig = $this->isAlreadybookedsubtrip(
            $piclocation,
            $droplocation,
            $subtripid,
            $outwardBusStartDate,
            $maitripid,
            $ticket_token,
            $requestedJourneyDate,
            (int) $this->session->get('pickstand')
        );

        if (!empty($checksubtripbookig)) {
            $requestedSeats = array_map('trim', array_filter(explode(',', (string) $this->session->get('seatnumbers'))));
            $intersection = array_intersect($requestedSeats, $checksubtripbookig);

            if (!empty($intersection)) {
                $this->db->transRollback();
                return redirect()->back()->withInput()->with('fail', admin_bilingual_lang('Localize.seat_already_booked_segment', ['seats' => implode(',', $intersection)]));
            }
        }

        // Check availability for return leg (return trip is submitted on the booking form)
        $returnSubtripId = (int) $this->request->getVar('subtripId');
        $returnSubtrip = $this->subtripModel->find($returnSubtripId);
        if ($returnSubtrip === null) {
            $this->db->transRollback();
            return redirect()->back()->withInput()->with('fail', 'Return trip details are missing or invalid. Please select the return journey again.');
        }

        $returnJourneyDateRaw = BookingInventorySupport::normalizeCalendarDate((string) $this->request->getVar('journeydate'));
        $returnPickStand = (int) $this->request->getVar('pickstand');
        $returnBusStartDate = $returnJourneyDateRaw;

        $checkReturn = $this->isAlreadybookedsubtrip(
            (int) $returnSubtrip->pick_location_id,
            (int) $returnSubtrip->drop_location_id,
            $returnSubtripId,
            $returnBusStartDate,
            (int) $returnSubtrip->trip_id,
            $ticket_token,
            $returnJourneyDateRaw,
            $returnPickStand
        );

        $returnRequestedSeats = array_map('trim', array_filter(explode(',', (string) $this->request->getVar('seatnumbers'))));
        $returnIntersection = array_intersect($returnRequestedSeats, $checkReturn);

        if (!empty($returnIntersection)) {
            $this->db->transRollback();
            return redirect()->back()->with('fail', admin_bilingual_lang('Localize.seat_already_booked_return_journey', ['seats' => implode(',', $returnIntersection)]));
        }



            $paymentStatus = $this->request->getVar('payment_status');



            if ($paymentStatus == "unpaid") {

                $singleTripPaymentStatus = "unpaid";

                $roundTripPaymentStatus = "unpaid";

                $paidamount = $this->session->get('grandtotal');

                $roundtripPaid = $this->request->getVar('grandtotal');

                $roundPaidamount = (float) $roundtripPaid - (float)$paidamount;

                $transectionSingeltrip = 0;

                $transectionDoubletrip = 0;

            }

            if ($paymentStatus == "paid") {

                $paidamount = $this->session->get('grandtotal');

                $roundtripPaid = $this->request->getVar('grandtotal');

                $roundPaidamount = (float) $roundtripPaid - (float)$paidamount;

                $singleTripPaymentStatus = "paid";

                $roundTripPaymentStatus = "paid";

                $transectionSingeltrip = $paidamount;

                $transectionDoubletrip =   $roundPaidamount;

            }

            if ($paymentStatus == "partial") {

                $partialamount = $this->request->getVar('partialpay');

                $singelTripamount = $this->session->get('grandtotal');

                if ($partialamount < $singelTripamount) {

                    $paidamount = $this->session->get('grandtotal');

                    $roundtripPaid = $this->request->getVar('grandtotal');

                    $roundPaidamount = (float) $roundtripPaid - (float)$paidamount;

                    $singleTripPaymentStatus = "partial";

                    $roundTripPaymentStatus = "unpaid";

                    $transectionSingeltrip = $partialamount;

                    $transectionDoubletrip = 0;

                }



                if ($partialamount == $singelTripamount) {



                    $paidamount = $this->session->get('grandtotal');

                    $roundtripPaid = $this->request->getVar('grandtotal');

                    $roundPaidamount = (float) $roundtripPaid - (float)$paidamount;

                    $singleTripPaymentStatus = "paid";

                    $roundTripPaymentStatus = "unpaid";

                    $transectionSingeltrip = $partialamount;

                    $transectionDoubletrip = 0;

                }



                if ($partialamount > $singelTripamount) {



                    $paidamount = $this->session->get('grandtotal');

                    $roundtripPaid = $this->request->getVar('grandtotal');

                    $roundPaidamount = (float) $roundtripPaid - (float)$paidamount;



                    $extraMoney = (float) $partialamount - (float) $paidamount;

                    $singleTripPaymentStatus = "paid";

                    $roundTripPaymentStatus = "partial";



                    $transectionSingeltrip = $paidamount;

                    $transectionDoubletrip = $extraMoney;

                }

            }







            $ticketbooking['payment_status'] = $singleTripPaymentStatus;

            $this->ticketModel->insert($ticketbooking);
            if (empty($this->ticketModel->getInsertID())) {
                $this->db->transRollback();

                return redirect()->back()->withInput()->with('fail', 'Outbound ticket could not be saved. Please try again.');
            }

            $partialPaid = array(

                "booking_id" => $rand,

                "trip_id" => $maitripid,

                "subtrip_id" => $subtripid,

                "passanger_id" => $userid,

                "paidamount" => $transectionSingeltrip,

                "pay_type_id" => $this->request->getVar('pay_method'),

            );

            $paidpartial = array(

                "booking_id" => $rand,

                "trip_id" => $maitripid,

                "subtrip_id" => $subtripid,

                "passanger_id" => $userid,

                "paidamount" => $transectionSingeltrip,

                "pay_type_id" => $this->request->getVar('pay_method'),

                "payment_detail" => $payment_detail,

            );



            if ($this->validation->run($partialPaid, 'partialpay')) {

                $this->partialpaidModel->insert($paidpartial);

                $pick_stand_id = $this->session->get('pickstand');

                $drop_stand_id = $this->session->get('dropstand');



                $journeylist = $this->journeylist($rand, $userid, $maitripid, $subtripid, $piclocation, $droplocation, $pick_stand_id, $drop_stand_id, $outwardBusStartDate);

                $userRole = $this->session->get('role_id');



                if (($userRole == 2) && ($singleTripPaymentStatus != "unpaid")) {

                    $totalprice =  $transectionSingeltrip;

                    $type = "income";

                    $message = "For Ticket Booking";

                    $agentIncome = agentIncomeCommission($backUserId, $totalprice, $rand, $subtripid, $userid, $message, $companyId);

                    $agentTotalIncome = agentTotal($backUserId, $totalprice, $rand, $type, $payment_detail_rocord);
                    
                                          
                    $agetnDetail =  $this->agentModel->where('user_id', $this->session->get('user_id'))->first();

                    $agentIncome = agentExpenseTicketCharge($agetnDetail->id, $totalprice, $rand, $subtripid, $userid, $message, $companyId);

                }

            }





            if ($singleTripPaymentStatus != "unpaid") {

                $paymethod_id = $this->request->getVar('pay_method');

                $payDetail = $this->request->getVar('paydetail');

                $type = "income";

                $detail = "Ticket Booking (" . $rand . ") ";

                accoutTranjection($type, $detail, $transectionSingeltrip, $backUserId, $rand, "booking");

                paymethodTeanjection($rand, $paymethod_id, $transectionSingeltrip, $payDetail, $maitripid, $subtripid, $backUserId);

            }





            $roundrand = substr(str_shuffle('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 1, 8);

            $roundrand = "TB" . $roundrand;

            $maitripid = null;

            $subtripid = null;

            $piclocation = null;

            $droplocation = null;

            $vehicles_id = null;

            $backUserId = $this->session->get('user_id');

            $subTripId = $this->request->getVar('subtripId');

            $payment_detail_rocord = $this->request->getVar('paydetail');



            $subtrips = $this->subtripModel->select('trips.id as tripid,trips.*,fleets.*,schedules.*,vehicles.*,subtrips.id as subtripId,subtrips.*,trips.company_id as companyId')

                ->join('trips', 'trips.id = subtrips.trip_id')

                ->join('fleets', 'fleets.id = trips.fleet_id')

                ->join('schedules', 'schedules.id = trips.schedule_id')

                ->join('vehicles', 'vehicles.id = trips.vehicle_id',"left")

                ->where('subtrips.status', 1)

                ->where('subtrips.id', $subTripId)

                ->findAll();
                
            $companyId = $subtrips[0]->companyId??0;


            foreach ($subtrips as $key => $value) {

                $maitripid = $value->tripid;

                $subtripid = $value->id;

                $piclocation = $value->pick_location_id;

                $droplocation = $value->drop_location_id;

                $vehicles_id = $value->vehicle_id;

            }





            $requestedReturnJourneyDate = BookingInventorySupport::normalizeCalendarDate((string) $this->request->getVar('journeydate'));
            $returnPickStand            = (int) $this->request->getVar('pickstand');
            $returnJourneyDateHelper    = new TripJourneyDateHelper();
            $returnBusStartDate         = $requestedReturnJourneyDate;
            $returnTicketDate           = BookingInventorySupport::ticketBoardingDateForStorage(
                (int) $maitripid,
                (int) $subtripid,
                $returnBusStartDate,
                $returnPickStand
            );
            
            $checkReturnSubtripBooking = $this->isAlreadybookedsubtrip(
                $piclocation,
                $droplocation,
                $subtripid,
                $returnBusStartDate,
                $maitripid,
                $ticket_token,
                $requestedReturnJourneyDate,
                (int) $this->request->getVar('pickstand')
            );

            if (!empty($checkReturnSubtripBooking)) {
                $returnRequestedSeats = array_map('trim', array_filter(explode(',', (string) $this->request->getVar('seatnumbers'))));
                $returnIntersection = array_intersect($returnRequestedSeats, $checkReturnSubtripBooking);

                if (!empty($returnIntersection)) {
                    $this->db->transRollback();
                    return redirect()->back()->withInput()->with('fail', admin_bilingual_lang('Localize.seat_already_booked_return_segment', ['seats' => implode(',', $returnIntersection)]));
                }
            }

            $roundticketbooking = array(

                "booking_id" => $roundrand,

                "trip_id" => $maitripid,

                "subtrip_id" => $subtripid,

                "passanger_id" => $userid,

                "pick_location_id" => $piclocation,

                "drop_location_id" => $droplocation,

                "pick_stand_id" => $this->request->getVar('pickstand'),

                "drop_stand_id" => $this->request->getVar('dropstand'),

                "price" => $this->request->getVar('totalprice'),

                "discount" =>  $getDiscount,

                "roundtrip_discount" =>  $this->request->getVar('roundtrip_discount'),

                "totaltax" => $this->request->getVar('tax'),

                "paidamount" =>  $roundPaidamount,

                "adult" => $this->request->getVar('aseat'),

                "chield" => $this->request->getVar('cseat'),

                "special" => $this->request->getVar('spseat'),

                "vip" => $this->request->getVar('vpseat')??"",

                "refund" => 0,

                "bookby_user_id" =>  $loginUserId,

                "bookby_user_type" => $bookUserType,

                "journeydata" => $returnTicketDate,

                "pay_type_id" => $this->request->getVar('pay_method'),

                // "payment_status" => $this->request->getVar('payment_status'),

                "payment_detail" => $this->request->getVar('paydetail'),

                "vehicle_id" => $vehicles_id,

                "cancel_status" => 0,



                "offerer" => $this->request->getVar('offerer'),

                // "offerer" => 0,

                "seatnumber" => $this->request->getVar('seatnumbers'),

                "totalseat" => $this->request->getVar('totalseat'),



            );



            $roundvalidTicketbooking = array(

                "booking_id" => $roundrand,

                "trip_id" => $maitripid,

                "subtrip_id" => $subtripid,

                "passanger_id" => $userid,



                "pick_location_id" => $piclocation,

                "drop_location_id" => $droplocation,

                "pick_stand_id" => $this->request->getVar('pickstand'),

                "drop_stand_id" => $this->request->getVar('dropstand'),



                "price" => $this->request->getVar('totalprice'),

                "paidamount" =>  $roundPaidamount,

                "seatnumber" => $this->request->getVar('seatnumbers'),

                "totalseat" => $this->request->getVar('totalseat'),

                "bookby_user_id" => 1,

                "journeydata" => $returnTicketDate,

                "pay_type_id" => $this->request->getVar('pay_method'),

                "payment_status" => $this->request->getVar('payment_status'),

                "vehicle_id" => $vehicles_id,



            );



            if ($this->validation->run($roundvalidTicketbooking, 'ticket')) {
                
                if(($userRole == 2) && ($roundTripPaymentStatus != "unpaid")){

                    $agetnDetail =  $this->agentModel->where('user_id', $this->session->get('user_id'))->first();

                    $walletAmount = $this->agentPayment->select('SUM(payagents.amount) as total_amount')

                    ->join('agents', 'agents.id = payagents.agent_id', 'left')

                    ->join('companies', 'companies.id = payagents.company_id', 'left')

                    ->where('payagents.agent_id', $agetnDetail->id)

                    ->where('companies.id ',$companyId)

                    ->where('payagents.status',1)

                    ->groupBy('companies.name')

                    ->first();

                    if (!is_object($walletAmount) || agent_wallet_insufficient_for_booking($walletAmount->total_amount ?? null, $transectionDoubletrip)){

                        $errmsg = admin_bilingual_lang('Localize.insufficient_balance');

                        $seatNumbers = $this->request->getVar('seatnumbers');

                        $this->db->transRollback();
                        $this->deleteHoldSeat($seatNumbers,$subtripid);

                        return redirect()->route('allbookinglist-ticket')->with("fail", $errmsg);

                    }

                }

                $partialPaid = array(

                    "booking_id" => $roundrand,

                    "trip_id" => $maitripid,

                    "subtrip_id" => $subtripid,

                    "passanger_id" => $userid,

                    "paidamount" => $transectionDoubletrip,

                    "pay_type_id" => $this->request->getVar('pay_method'),

                );



                $paidpartial = array(

                    "booking_id" => $roundrand,

                    "trip_id" => $maitripid,

                    "subtrip_id" => $subtripid,

                    "passanger_id" => $userid,

                    "paidamount" => $transectionDoubletrip,

                    "pay_type_id" => $this->request->getVar('pay_method'),

                    "payment_detail" => $this->request->getVar('paydetail'),

                );





                $roundticketbooking['payment_status'] = $roundTripPaymentStatus;

                $this->ticketModel->insert($roundticketbooking);
                if (empty($this->ticketModel->getInsertID())) {
                    $this->db->transRollback();

                    return redirect()->back()->withInput()->with('fail', 'Return ticket could not be saved. Please try again.');
                }

                if ($this->validation->run($partialPaid, 'partialpay')) {



                    $this->partialpaidModel->insert($paidpartial);



                    $pick_stand_id = $this->request->getVar('pickstand');

                    $drop_stand_id = $this->request->getVar('dropstand');



                    $journeylist = $this->journeylist($roundrand, $userid, $maitripid, $subtripid, $piclocation, $droplocation, $pick_stand_id, $drop_stand_id, $returnBusStartDate);



                    $userRole = $this->session->get('role_id');







                    if (($userRole == 2) && ($roundTripPaymentStatus != "unpaid")) {

                        $totalprice =  $transectionDoubletrip;

                        $type = "income";

                        $message = "For Ticket Booking";



                        $agentIncome = agentIncomeCommission($backUserId, $totalprice, $roundrand, $subtripid, $userid, $message, $companyId);



                        $agentTotalIncome =   agentTotal($backUserId, $totalprice, $roundrand, $type, $payment_detail_rocord);
                        
                        $agetnDetail =  $this->agentModel->where('user_id', $this->session->get('user_id'))->first();

                        $agentIncome = agentExpenseTicketCharge($agetnDetail->id, $totalprice, $roundrand, $subtripid, $userid, $message, $companyId);
                        

                    }

                }

            }



            if ($roundTripPaymentStatus != "unpaid") {

                $paymethod_id = $this->request->getVar('pay_method');

                $payDetail = $this->request->getVar('paydetail');

                $type = "income";

                $detail = "Ticket Booking (" . $roundrand . ") ";

                accoutTranjection($type, $detail, $transectionDoubletrip, $backUserId, $roundrand, "booking");

                paymethodTeanjection($rand, $paymethod_id, $transectionDoubletrip, $payDetail, $maitripid, $subtripid, $backUserId);

            }



            $couponcode = $this->request->getVar('offerer');

            if (!empty($couponcode)) {

                $validDetail = $this->couponModel->where('code', $couponcode)->findAll();





                $coupondetail = array(

                    "code" => $couponcode,

                    "coupon_id" => $validDetail[0]->id,

                    "booking_id" => $rand,

                    "subtrip_id" => $subtripid,

                    "amount" => $validDetail[0]->discount,



                );



                $coupondetailround = array(

                    "code" => $couponcode,

                    "coupon_id" => $validDetail[0]->id,

                    "booking_id" => $roundPaidamount,

                    "subtrip_id" => $subtripid,

                    "amount" => $validDetail[0]->discount,



                );



                $this->coupondiscountModel->insert($coupondetail);

                $this->coupondiscountModel->insert($coupondetailround);

            }





            $this->db->transComplete();

            try {
                $emaildata = $ticketmailLibrary->getticketEmailData($rand);
                $roundemaildata = $ticketmailLibrary->getticketEmailData($roundrand);
                if (! empty($emaildata['ticket'])) {
                    sendTicket($login_email, $emaildata);
                }
                if (! empty($roundemaildata['ticket'])) {
                    sendTicket($login_email, $roundemaildata);
                }
            } catch (\Throwable $e) {
                log_message('error', 'Round-trip ticket email failed: {message}', [
                    'message' => $e->getMessage(),
                ]);
            }

            $status = true;

            $rouondstatus = true;



            if (($status == true) && ($rouondstatus == true)) {

                $this->session->remove('subtripId');

                $this->session->remove('seatnumbers');

                $this->session->remove('totalseat');

                $this->session->remove('journeydate');

                $this->session->remove('aseat');

                $this->session->remove('spseat');

                $this->session->remove('vpseat');

                $this->session->remove('cseat');

                $this->session->remove('totalprice');

                $this->session->remove('tax');

                $this->session->remove('grandtotal');

                $this->session->remove('pickstand');

                $this->session->remove('dropstand');

                $this->session->remove('isrountrip');

                $this->session->remove('discountPercent');

                $this->session->remove('roundtrip_discount');

                return redirect()->route('allbookinglist-ticket')->with("success", "Ticket Booking");
            }

        $this->db->transRollback();

        return redirect()->route('new-ticket')->with("fail", $this->validation->getError());

    }

    

    public function pretrip()

    {

        $company_id = session()->get('company_id') ?? 0;

        if(($this->request->getVar('company_id') || $this->request->getVar('company_id') == 0) && session()->get('role_id') == "1"){

            $data['companies'] = $this->CompanyModel->findAll();

        }else{

            $company_ids = explode(',', $company_id);

            $data['companies'] = $this->CompanyModel->whereIn('id', $company_ids)->findAll();

        }

        return view($this->Viewpath . '\ticket\pretrip', $data);

    }

    

    public function pre_trip_booking()

    {

        $loginUserId = $this->session->get('user_id');

        $data1['trip_id'] = $this->request->getVar('tripid');

        $data1['Driver_id'] = $this->request->getVar('Driver_id');

        $data1['Assistant_id'] = $this->request->getVar('Assistant_id');

        $data1['bus_id'] = $this->request->getVar('bus_id');

        $data1['Number_of_Seats'] = $this->request->getVar('Number_of_Seats');

        $data1['gas_later'] = $this->request->getVar('Gas_later');

        $data1['bus_meter'] = $this->request->getVar('Bus_meter');

        $data1['notes'] = $this->request->getVar('notes');

        $data1['created_by'] = $loginUserId;

        $data1['create_at'] = $this->request->getVar('pretripDate');

        $data1['pretrip_id'] = 1;



        $tripId = $this->request->getVar('tripid');

        $trip = $this->tripModel->find($tripId);

        

        if ($trip) {

            $companyTripIds = $this->tripModel

                ->select('id')

                ->where('company_id', $trip->company_id)

                ->findColumn('id');



            $maxPretripId = $this->pretripModel

                ->selectMax('pretrip_id')

                ->whereIn('trip_id', $companyTripIds)

                ->first();

            $data1['pretrip_id'] = ($maxPretripId->pretrip_id ?? 0) + 1;

        }

        $today = egypt_today();

        $existing = $this->pretripModel

            ->where('trip_id', $data1['trip_id'])

            ->where("DATE(create_at)", $this->request->getVar('pretripDate'))

            ->first();



        if ($existing) {

            return redirect()->route('pre_trip_list')->with("fail", "pretrip is already created.");

        }

        

        $this->pretripModel->insert($data1);



        $data2['trip_id']       = $this->request->getVar('tripid');

        $data2['employee_id']   = $this->request->getVar('Driver_id');

        $data2['employee_type']   = 1;//static for driver

        $data2['created_at']   = $this->request->getVar('pretripDate');

        $this->stuffassignModel->insert($data2);



        $data3['trip_id']       = $this->request->getVar('tripid');

        $data3['employee_id']   = $this->request->getVar('Assistant_id');

        $data3['employee_type']   = 2;//static for Assistant

        $data3['created_at']   = $this->request->getVar('pretripDate');

        $this->stuffassignModel->insert($data3);

        

        // $data['fleet_type'] = $this->fleetTypeModel->findAll();

        // $data['vehicle'] = $this->vehicleModel->findAll();

            

            

        // $data['trip'] = $this->tripModel->select('trips.id as tripid, trips.*, schedules.*, vehicles.*, pic_location.name as pic_location_name, drop_location.name as drop_location_name')

        //         ->join('schedules', 'schedules.id = trips.schedule_id')

        //         ->join('vehicles', 'vehicles.id = trips.vehicle_id')

        //         ->join('locations as pic_location', 'pic_location.id = trips.pick_location_id', 'left')

        //         ->join('locations as drop_location', 'drop_location.id = trips.drop_location_id', 'left')

        //         ->findAll();

            

        // $data['driver'] = $this->employeeModel->where('employeetype_id', 1)->findAll();

        // $data['assistant'] = $this->employeeModel->where('employeetype_id', 2)->findAll();

        

        // print_r($data['trip'][0]);die;

        // reg_no

        

        return redirect()->route('pre_trip_list')->with("success", "pretrip created");

        

        // return view($this->Viewpath . '\ticket\pretrip', $data);

    }

    

    public function pre_trip_list()

    {

        return view($this->Viewpath . '\ticket\pretriplist');

    }

    

    public function edit_pretrip($id)

    {

        $data['pretrip'] = $this->pretripModel->select('pretrip.id as pretripId, pretrip.*, trips.*, driver.first_name as driverName, assistance.first_name as assistanceName, vehicles.*, schedules.*, pic_location.name as pic_location_name, drop_location.name as drop_location_name')

                ->join('trips', 'pretrip.trip_id = trips.id')

                ->join('employees AS driver', 'pretrip.Driver_id = driver.id')

                ->join('employees AS assistance', 'pretrip.Assistant_id = assistance.id')

                ->join('vehicles', 'pretrip.bus_id = vehicles.id')

                ->join('schedules', 'trips.schedule_id = schedules.id')

                ->join('locations as pic_location', 'pic_location.id = trips.pick_location_id', 'left')

                ->join('locations as drop_location', 'drop_location.id = trips.drop_location_id', 'left')

                ->where('pretrip.deleted_at IS NULL')

                ->where('pretrip.id', $id)

                ->find();

        if (empty($data['pretrip'])) {
            return redirect()->route('pre_trip_list')->with('fail', 'Pre-trip not found or already deleted.');
        }

        $companyId = $data['pretrip'][0]->company_id ?? null;

        $tripBuilder = $this->tripModel->select('trips.id as tripid, trips.*, schedules.*, vehicles.*, pic_location.name as pic_location_name, drop_location.name as drop_location_name')
                ->join('schedules', 'schedules.id = trips.schedule_id')
                ->join('vehicles', 'vehicles.id = trips.vehicle_id')
                ->join('locations as pic_location', 'pic_location.id = trips.pick_location_id', 'left')
                ->join('locations as drop_location', 'drop_location.id = trips.drop_location_id', 'left');

        if ($companyId) {
            $tripBuilder->where('trips.company_id', $companyId);
        }

        $data['trip'] = $tripBuilder->findAll();

        $driverBuilder = $this->employeeModel->where('employeetype_id', 1);
        $assistantBuilder = $this->employeeModel->where('employeetype_id', 2);
        $vehicleBuilder = $this->vehicleModel;

        if ($companyId) {
            $driverBuilder->where('company_id', $companyId);
            $assistantBuilder->where('company_id', $companyId);
            $vehicleBuilder->where('company_id', $companyId);
        }

        $data['driver'] = $driverBuilder->findAll();
        $data['assistant'] = $assistantBuilder->findAll();
        $data['vehicle'] = $vehicleBuilder->findAll();

        return view($this->Viewpath . '\ticket\pretrip_edit', $data);

    }

    

    public function edit_pre_trip_booking()

    {

        $id = $this->request->getVar('pretripId');

        $pretrip = $this->pretripModel->find($id);

        $data['trip_id'] = $this->request->getVar('tripid');

        $data['Driver_id'] = $this->request->getVar('Driver_id');

        $data['Assistant_id'] = $this->request->getVar('Assistant_id');

        $data['bus_id'] = $this->request->getVar('bus_id');

        $data['Number_of_Seats'] = $this->request->getVar('Number_of_Seats');
        $data['gas_later'] = $this->request->getVar('Gas_later');
        $data['bus_meter'] = $this->request->getVar('Bus_meter');

        $this->pretripModel->update($id, $data);

        // Update staff assignments as well
        if ($pretrip) {
            $oldTripId = $pretrip->tripid ?? $pretrip->trip_id;
            $oldDate = date('Y-m-d', strtotime($pretrip->create_at));

            // Update Driver
            $this->stuffassignModel->where('trip_id', $oldTripId)
                ->where('DATE(created_at)', $oldDate)
                ->where('employee_type', 1)
                ->set(['employee_id' => $data['Driver_id']])
                ->update();

            // Update Assistant
            $this->stuffassignModel->where('trip_id', $oldTripId)
                ->where('DATE(created_at)', $oldDate)
                ->where('employee_type', 2)
                ->set(['employee_id' => $data['Assistant_id']])
                ->update();
        }

        

        return redirect()->route('pre_trip_list')->with("success", "pretrip updated");

    }

    

    public function delete_pretrip($id)

    {
        $pretrip = $this->pretripModel->find($id);
        if ($pretrip) {
            $tripId = $pretrip->tripid ?? $pretrip->trip_id;
            $date = date('Y-m-d', strtotime($pretrip->create_at));

            // Also delete stuff assignments for this trip and date
            $this->stuffassignModel->where('trip_id', $tripId)
                ->where('DATE(created_at)', $date)
                ->delete();
        }

        if ($this->pretripModel->delete($id)) {

            return redirect()->route('pre_trip_list')->with("success", "pretrip deleted");

        } else {

            return redirect()->route('pre_trip_list')->with("fail", "not delete");

        }

    }

    public function getDynamicPickupDropCombinations($pickup, $drop, $tripSeries, $date, $tripId, $requestingSubtripId = null, $userJourneyDate = null, $pickStandId = 0)
    {
        $this->db = \Config\Database::connect();

        $stops = empty($tripSeries) ? [] : array_values(array_filter(array_map('trim', explode(',', (string) $tripSeries))));
        if (empty($stops)) {
            return [];
        }

        // Regular booking: use the requested subtrip pick/drop only (do not expand to full trip span).
        $spanPick = $pickup;
        $spanDrop = $drop;

        if (!$this->canResolveSegment($stops, $spanPick, $spanDrop)) {
            return [];
        }

        $requestStoppage = '';
        if ((int) ($requestingSubtripId ?? 0) > 0) {
            $reqSub = $this->subtripModel->find((int) $requestingSubtripId);
            $requestStoppage = $reqSub ? trim((string) ($reqSub->stoppage ?? '')) : '';
        }

        $departureDates = BookingInventorySupport::resolveSearchDepartureDates(
            (int) $tripId,
            (int) ($requestingSubtripId ?? 0),
            (string) $date,
            (int) $pickStandId
        );

        $builder = $this->db->table('tickets');
        $builder->select('tickets.subtrip_id, tickets.pick_location_id, tickets.drop_location_id, tickets.seatnumber, tickets.journeydata, tickets.pick_stand_id, subtrips.pick_location_id AS subtrip_pick_id, subtrips.drop_location_id AS subtrip_drop_id, subtrips.stoppage AS subtrip_stoppage')
            ->join('subtrips', 'tickets.subtrip_id = subtrips.id', 'LEFT')
            ->join('trips', 'tickets.trip_id = trips.id', 'LEFT')
            ->where('tickets.cancel_status', 0)
            ->where('tickets.refund', 0)
            ->where('trips.status', 1)
            ->where('tickets.trip_id', $tripId);

        BookingInventorySupport::applyTicketJourneyDateFilter($builder, (int) $tripId, $departureDates);

        $rows = $builder->get()->getResultArray();

        $filtered = [];
        $busStart = $departureDates['bus_start'];
        foreach ($rows as $ticket) {
            $ticketSubtripId = (int) ($ticket['subtrip_id'] ?? 0) ?: (int) ($requestingSubtripId ?? 0);
            if (! BookingInventorySupport::ticketBelongsToBusRun(
                (int) $tripId,
                $ticketSubtripId,
                (string) ($ticket['journeydata'] ?? ''),
                (int) ($ticket['pick_stand_id'] ?? 0),
                $busStart
            )) {
                continue;
            }

            $segment = $this->resolveBookingSegmentForOverlap($stops, $ticket);
            if ($segment === null) {
                continue;
            }

            if ($this->segmentsOverlapInStops(
                $stops,
                $spanPick,
                $spanDrop,
                $segment['pick'],
                $segment['drop'],
                $requestStoppage,
                (string) ($ticket['subtrip_stoppage'] ?? '')
            )) {
                $filtered[] = $ticket;
            }
        }

        return $filtered;
    }

    /**
     * Resolve the booked segment for overlap checks (ticket pick/drop first, then subtrip, then expand).
     *
     * @param array<int,string> $stops
     * @param array<string,mixed> $ticket
     * @return array{pick:string,drop:string}|null
     */
    private function resolveBookingSegmentForOverlap(array $stops, array $ticket): ?array
    {
        $candidates = [];

        $ticketPick = (string) ($ticket['pick_location_id'] ?? '');
        $ticketDrop = (string) ($ticket['drop_location_id'] ?? '');
        if ($ticketPick !== '' && $ticketDrop !== '') {
            $candidates[] = [$ticketPick, $ticketDrop];
        }

        $subPick = (string) ($ticket['subtrip_pick_id'] ?? '');
        $subDrop = (string) ($ticket['subtrip_drop_id'] ?? '');
        if ($subPick !== '' && $subDrop !== '') {
            $candidates[] = [$subPick, $subDrop];
        }

        foreach ($candidates as [$pick, $drop]) {
            if ($this->getSegmentRange($stops, $pick, $drop) !== null) {
                return ['pick' => $pick, 'drop' => $drop];
            }
        }

        // Confirmed tickets: only ticket/subtrip pick→drop on main axis (no span expand).
        return null;
    }

    /**
     * Expand subtrip pick/drop (+ subtrip stoppage) to the full span on the main trip stop list.
     * Used for temporary holds / admin block seats, not for confirmed ticket overlap.
     *
     * @param array<int,string> $stops
     * @param object|array|null $subtrip
     * @return array{pick:string,drop:string}|null
     */
    private function expandSubtripToTripSpan(array $stops, $subtrip): ?array
    {
        if (empty($stops) || $subtrip === null) {
            return null;
        }

        $pick = (string) (is_array($subtrip) ? ($subtrip['pick_location_id'] ?? '') : ($subtrip->pick_location_id ?? ''));
        $drop = (string) (is_array($subtrip) ? ($subtrip['drop_location_id'] ?? '') : ($subtrip->drop_location_id ?? ''));
        $stoppageRaw = is_array($subtrip) ? ($subtrip['stoppage'] ?? '') : ($subtrip->stoppage ?? '');
        $extra = array_filter(array_map('trim', explode(',', (string) $stoppageRaw)));

        $ids = array_unique(array_filter(array_merge([$pick, $drop], $extra)));

        $positionMap = [];
        foreach ($stops as $idx => $stopId) {
            $positionMap[(string) $stopId] = $idx;
        }

        $indices = [];
        foreach ($ids as $id) {
            if ($id !== '' && isset($positionMap[$id])) {
                $indices[] = $positionMap[$id];
            }
        }

        if (empty($indices)) {
            return null;
        }

        $minIdx = min($indices);
        $maxIdx = max($indices);

        return [
            'pick' => (string) $stops[$minIdx],
            'drop' => (string) $stops[$maxIdx],
        ];
    }

    public function getDynamicPickupDropCombinationsTemp($pickup, $drop, $tripSeries, $date, $subtripId, $ignoreToken = null, $onlyToken = null, $userJourneyDate = null, $pickStandId = 0)
    {
        (new \App\Libraries\TemporaryBookHoldService())->cleanupExpiredHolds();
        $this->db = \Config\Database::connect();

        $sub = $this->subtripModel->find($subtripId);
        $tripId = $sub ? (int) $sub->trip_id : 0;

        $stops = empty($tripSeries) ? [] : array_values(array_filter(array_map('trim', explode(',', (string) $tripSeries))));
        if (empty($stops)) {
            return [];
        }

        $spanPick = $pickup;
        $spanDrop = $drop;

        if (!$this->canResolveSegment($stops, $spanPick, $spanDrop)) {
            return [];
        }

        $requestStoppage = $sub ? trim((string) ($sub->stoppage ?? '')) : '';

        $departureDates = BookingInventorySupport::resolveSearchDepartureDates(
            $tripId,
            (int) $subtripId,
            (string) $date,
            (int) $pickStandId
        );

        $builder = $this->db->table('temporarybooks');
        $builder->select('temporarybooks.seat_names, temporarybooks.journey_date, subtrips.pick_location_id, subtrips.drop_location_id, subtrips.stoppage AS existing_stoppage')
            ->join('subtrips', 'temporarybooks.subtrip_id = subtrips.id', 'LEFT')
            ->join('trips', 'subtrips.trip_id = trips.id', 'LEFT');
        BookingInventorySupport::applyTemporaryBookJourneyDateFilter($builder, $departureDates);
        \App\Libraries\TemporaryBookHoldService::applyActiveHoldExpiryFilter($builder);
        $builder->where('trips.status', 1)
            ->where('subtrips.trip_id', $tripId);

        if ($onlyToken !== null && trim((string) $onlyToken) !== '') {
            $builder->where('TRIM(temporarybooks.ticket_token)', trim((string) $onlyToken));
        } elseif ($ignoreToken) {
            $builder->where('TRIM(temporarybooks.ticket_token) !=', trim((string) $ignoreToken));
        }

        $rows = $builder->get()
            ->getResultArray();

        $filtered = [];
        $busStart = $departureDates['bus_start'];
        foreach ($rows as $tbook) {
            if (! BookingInventorySupport::holdBelongsToBusRun((string) ($tbook['journey_date'] ?? ''), $busStart)) {
                continue;
            }

            $segment = $this->resolveBookingSegmentForOverlap($stops, [
                'pick_location_id' => $tbook['pick_location_id'] ?? '',
                'drop_location_id' => $tbook['drop_location_id'] ?? '',
                'subtrip_pick_id' => $tbook['pick_location_id'] ?? '',
                'subtrip_drop_id' => $tbook['drop_location_id'] ?? '',
                'subtrip_stoppage' => $tbook['existing_stoppage'] ?? '',
            ]);

            if ($segment !== null && $this->segmentsOverlapInStops(
                $stops,
                $spanPick,
                $spanDrop,
                $segment['pick'],
                $segment['drop'],
                $requestStoppage,
                (string) ($tbook['existing_stoppage'] ?? '')
            )) {
                $filtered[] = $tbook;
            }
        }

        return $filtered;
    }

    /**
     * Flatten temporarybooks rows into individual seat numbers (e.g. "8,9" -> ["8","9"]).
     */
    private function flattenTemporarySeatRows(array $rows): array
    {
        $out = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $names = isset($row['seat_names']) ? (string) $row['seat_names'] : '';
            foreach (array_filter(array_map('trim', explode(',', $names))) as $s) {
                if ($s !== '') {
                    $out[] = $s;
                }
            }
        }

        return array_values(array_unique($out));
    }

    private function filterSegmentOverlaps(array $rows, array $stops, $pickup, $drop, string $pickKey, string $dropKey): array
    {
        if (empty($rows) || empty($stops)) {
            return [];
        }

        if (!$this->canResolveSegment($stops, $pickup, $drop)) {
            return [];
        }

        $filtered = [];
        foreach ($rows as $row) {
            $start = (string) ($row[$pickKey] ?? '');
            $end = (string) ($row[$dropKey] ?? '');

            if ($this->segmentsOverlapInStops($stops, $pickup, $drop, $start, $end)) {
                $filtered[] = $row;
                continue;
            }

            $existingStops = array_values(array_filter(array_map('trim', explode(',', (string) ($row['existing_stoppage'] ?? '')))));
            if ($this->segmentsOverlapInStops($existingStops, $pickup, $drop, $start, $end)) {
                $filtered[] = $row;
            }
        }

        return $filtered;
    }

    private function canResolveSegment(array $stops, $pickup, $drop): bool
    {
        return $this->getSegmentRange($stops, $pickup, $drop) !== null;
    }

    private function stopIndexOnRoute(array $stops, $locationId): ?int
    {
        foreach ($stops as $idx => $stopId) {
            if ((string) $stopId === (string) $locationId) {
                return (int) $idx;
            }
        }

        return null;
    }

    /**
     * Stop indices used while traveling pick → drop (both directions on main route).
     *
     * @return list<int>
     */
    private function traveledStopIndices(array $stops, $pick, $drop): array
    {
        $pickIdx = $this->stopIndexOnRoute($stops, $pick);
        $dropIdx = $this->stopIndexOnRoute($stops, $drop);
        if ($pickIdx === null || $dropIdx === null || $pickIdx === $dropIdx) {
            return [];
        }

        if ($pickIdx < $dropIdx) {
            return range($pickIdx, $dropIdx);
        }

        return range($dropIdx, $pickIdx);
    }

    private function segmentsOverlapInStops(
        array $stops,
        $pickup,
        $drop,
        $existingPickup,
        $existingDrop,
        ?string $requestSubtripStoppage = null,
        ?string $existingSubtripStoppage = null
    ): bool {
        return SegmentSeatAvailability::segmentsOverlapWithPath(
            $stops,
            $pickup,
            $drop,
            $requestSubtripStoppage,
            $existingPickup,
            $existingDrop,
            $existingSubtripStoppage
        );
    }

    private function getSegmentRange(array $stops, $pickup, $drop): ?array
    {
        if (empty($stops)) {
            return null;
        }

        $positionMap = [];
        foreach ($stops as $idx => $stopId) {
            $positionMap[(string) $stopId] = $idx;
        }

        $pickupKey = (string) $pickup;
        $dropKey = (string) $drop;
        if (!isset($positionMap[$pickupKey]) || !isset($positionMap[$dropKey])) {
            return null;
        }

        $min = min($positionMap[$pickupKey], $positionMap[$dropKey]);
        $max = max($positionMap[$pickupKey], $positionMap[$dropKey]);

        return $min === $max ? null : [$min, $max];
    }



    public function isDateInRange($dateToCheck, $startDate, $endDate) {

        // Convert dates to DateTime objects

        $date       = new \DateTime($dateToCheck);

        $start      = new \DateTime($startDate);

        $end        = new \DateTime($endDate);



        // Check if the date is in the range

        return ($date >= $start && $date <= $end);

    }



    public function print_pretrip($id)

    {

        $data['pretrip'] = $this->pretripModel->select('pretrip.id as pretripId, pretrip.*, trips.*, CONCAT(driver.first_name," ",driver.last_name) as driverName, driver.phone as driverphone, CONCAT(assistance.first_name," ",assistance.last_name) as assistanceName, assistance.phone as assistancephone, vehicles.*, schedules.*, pic_location.name as pic_location_name, drop_location.name as drop_location_name, pretrip.gas_later as gas_later, pretrip.bus_meter as bus_meter, pretrip.notes as notes, CONCAT(COALESCE(booker.first_name, booker_agents.first_name, booker_agents_emp.first_name), " ", COALESCE(booker.last_name, booker_agents.last_name, booker_agents_emp.last_name)) AS createdBy, companies.name as company_name, pretrip.create_at as pretrip_created_at, pretrip.updated_at as pretrip_updated_at, booker_agents.id as booker_id, booker_agents.id as booker_agent_id, booker_agents_emp.id as booker_agents_emp_id,users.role_id as booker_agents_emp_role, roles.name AS role_name

            ')

                ->join('trips', 'pretrip.trip_id = trips.id')

                ->join('employees AS driver', 'pretrip.Driver_id = driver.id')

                ->join('employees AS assistance', 'pretrip.Assistant_id = assistance.id',"left")

                ->join('vehicles', 'pretrip.bus_id = vehicles.id')

                ->join('schedules', 'trips.schedule_id = schedules.id')

                ->join('user_details as booker', 'pretrip.created_by = booker.user_id', "left")

                ->join('agents as booker_agents', 'pretrip.created_by = booker_agents.user_id', "left")

                ->join('locations as pic_location', 'pic_location.id = trips.pick_location_id', 'left')

                ->join('locations as drop_location', 'drop_location.id = trips.drop_location_id', 'left')

                ->join('companies', "trips.company_id = companies.id","left")

                ->join("users","users.id = pretrip.created_by","left")

                ->join('roles', 'users.role_id = roles.id',"left")

                ->join("employees AS booker_agents_emp","booker_agents_emp.email = users.login_email","left")

                // ->where('DATE(pretrip.create_at)',date('Y-m-d'))

                ->where('pretrip.deleted_at IS NULL')

                ->where('pretrip.id',$id)

                ->findAll();

        if (empty($data['pretrip'])) {
            return redirect()->route('pre_trip_list')->with('fail', 'Pre-trip not found or already deleted.');
        }

        $dropLocationIdsCsv = $data['pretrip'][0]->stoppage?? '';

        if ($dropLocationIdsCsv) {

            $dropIdsArray = explode(',', $dropLocationIdsCsv);

            $stoppageNames = $this->db->table('locations')

                ->select('GROUP_CONCAT(name ORDER BY FIELD(id, ' . $dropLocationIdsCsv . ') SEPARATOR ", ") AS stoppage')

                ->whereIn('id', $dropIdsArray)

                ->get()

                ->getRow();

            $data['pretrip'][0]->dropLocations = $stoppageNames->stoppage ?? '';

        } else {

            // $data['pretrip'][0]?->dropLocations = '';

            if (isset($data['pretrip'][0])) {

                $data['pretrip'][0]->dropLocations = '';

            }

            

        }



        // $boarding = $this->picdropModel->select('pickdrops.id as pickdropid,pickdrops.*,stands.*')

        //     ->join('stands', 'stands.id = pickdrops.stand_id')

        //     ->where('trip_id', $data['pretrip'][0]->trip_id)

        //     ->where('pickdrops.type', 1)

        //     ->first();

        // $dropping = $this->picdropModel->select('pickdrops.id as pickdropid,pickdrops.*,stands.*')

        //     ->join('stands', 'stands.id = pickdrops.stand_id')

        //     ->where('trip_id', $data['pretrip'][0]->trip_id)

        //     ->where('pickdrops.type', 0)

        //     ->first();

        // $data['pretrip'][0]->boarding = $boarding->name ??"";

        // $data['pretrip'][0]->dropping = $dropping->name ??"";



        // Earliest pickup (boarding)

        $boardingList = $this->picdropModel->select('pickdrops.id as pickdropid, pickdrops.*, stands.*, stands.name as stand_name')

            ->join('stands', 'stands.id = pickdrops.stand_id')

            ->where('trip_id', $data['pretrip'][0]->trip_id)

            ->where('pickdrops.type', 1)

            ->findAll();



        $boarding = null;

        foreach ($boardingList as $item) {

            if (is_null($boarding) || strtotime($item->time) < strtotime($boarding->time)) {

                $boarding = $item;

            }

        }



        // Latest drop (dropping)

        $droppingList = $this->picdropModel->select('pickdrops.id as pickdropid, pickdrops.*, stands.*, stands.name as stand_name')

            ->join('stands', 'stands.id = pickdrops.stand_id')

            ->where('trip_id', $data['pretrip'][0]->trip_id)

            ->where('pickdrops.type', 0)

            ->findAll();



        $dropping = null;

        foreach ($droppingList as $item) {

            if (is_null($dropping) || strtotime($item->time) > strtotime($dropping->time)) {

                $dropping = $item;

            }

        }



        $data['pretrip'][0]->boarding = $boarding->name ?? "";

        $data['pretrip'][0]->dropping = $dropping->name ?? "";











        // $data['module'] =    lang("Localize.ticket_booking");

        // $data['title']  =    lang("Localize.journey_list");

        return view($this->Viewpath . '\ticket\pretripprint', $data);

    }

    public function getTripsByCompany($companyId)

    {

        $trips = $this->tripModel->select('trips.id as tripid, trips.*, schedules.*, vehicles.*, pic_location.name as pic_location_name, drop_location.name as drop_location_name')

                ->join('schedules', 'schedules.id = trips.schedule_id','left')

                ->join('vehicles', 'vehicles.id = trips.vehicle_id','left')

                ->join('locations as pic_location', 'pic_location.id = trips.pick_location_id', 'left')

                ->join('locations as drop_location', 'drop_location.id = trips.drop_location_id', 'left')

                ->where('trips.company_id', $companyId)

                ->findAll();

        echo json_encode($trips);

        exit;

    }

    public function getTripsVehicle($tripID, $companyId){

        $tripDetails = $this->tripModel

                            ->select('trips.fleet_id as fleetID')

                            ->where('trips.id', $tripID)

                            ->first();



    if ($tripDetails) {

        $fleetID = $tripDetails->fleetID;

        $VehicleDetails = $this->vehicleModel

            ->select('vehicles.*')

            ->where('fleet_id', $fleetID)

            ->where('company_id', $companyId)

            ->findAll();

        } else {

            $VehicleDetails = [];

        }

        echo json_encode($VehicleDetails);

        exit;

    }



    public function getVehicleDetails($vehicleId){

        $VehicleDetails = $this->vehicleModel

            ->select('vehicles.*, fleets.*')

            ->join('fleets', 'fleets.id = vehicles.fleet_id')

            ->where('vehicles.id', $vehicleId)

            ->first();

        echo json_encode($VehicleDetails);

        exit;

    }



    public function getDriverDetails($companyId){

        $Driver = $this->employeeModel->where('employeetype_id', 1)->where('company_id', $companyId)->findAll();

        echo json_encode($Driver);

        exit;

    }



    public function getAssistantDetails($companyId){

        $assistant = $this->employeeModel->where('employeetype_id', 2)->where('company_id', $companyId)->findAll();

        echo json_encode($assistant);

        exit;

    }

    // public function deleteHoldSeat(int $seatNumber,int $subtripId)

    // {

    //     $subtrip_id = $subtripId;



    //     if($this->temporaryBookModel->where('seat_names', $seatNumber)->where('subtrip_id',$subtrip_id)->delete()){

    //         return $this->response->setJSON([

    //             'status' => 'success',

    //             'response' => 200,

    //             'message' => true

    //         ]);    

    //     }else{

    //         return $this->response->setJSON([

    //             'status' => 'failed',

    //             'response' => 204,

    //             'message' => true

    //         ]);            

    //     }

        

    // }

    public function deleteHoldSeat(string $seatNumber, int $subtripId)
    {
        BookingInventorySupport::syncBookingTimezone();
        (new TemporaryBookHoldService())->cleanupExpiredHolds();

        $seat_names = TemporaryBookHoldService::normalizeSeatNamesForMatch($seatNumber);
        $journey_date = trim((string) ($this->request->getVar('journeydate') ?? $this->request->getVar('journey_date') ?? ''));
        $ticket_token = trim((string) ($this->request->getVar('ticket_token') ?? ''));
        $pickstand = (int) ($this->request->getVar('pickstand') ?? 0);
        $temporary_book_id = (int) ($this->request->getVar('temporary_book_id') ?? $this->request->getVar('id') ?? 0);

        $deleted = (new TemporaryBookHoldService())->releaseHold(
            $temporary_book_id > 0 ? $temporary_book_id : null,
            $subtripId > 0 ? $subtripId : null,
            $ticket_token !== '' ? $ticket_token : null,
            $seat_names !== '' ? $seat_names : null,
            $journey_date !== '' ? $journey_date : null,
            $pickstand
        );

        if ($deleted > 0) {
            return $this->response->setJSON([
                'status' => 'success',
                'response' => 200,
                'message' => true,
            ]);
        }

        return $this->response->setJSON([
            'status' => 'failed',
            'response' => 204,
            'message' => 'Seat not unHolded',
        ]);
    }

    function isAlreadybooked($tripid, $subtripid, $userid, $piclocation, $droplocation, $totalseat, $seatNumbers, $journeydate)

    {

        return $this->ticketModel

            ->where('cancel_status', 0)
            
            ->where('refund', 0)

            ->where('trip_id', $tripid)

            ->where('subtrip_id', $subtripid)

            ->where('passanger_id', $userid)

            ->where('pick_location_id', $piclocation)

            ->where('drop_location_id', $droplocation)

            ->where('totalseat', $totalseat)

            ->where('seatnumber', $seatNumbers)

            ->where('journeydata',$journeydate)

            ->first();

    }


    public function serverList()
    {
        $request = service('request');

        // Role and company ID from session
        $userRole = (int) session()->get('role_id');
        $userId = session()->get('user_id');
        $company_id = session()->get('company_id');

        // Check permissions
        $rolepermissionLibrary = new Rolepermission();
        $refund_create = $rolepermissionLibrary->create("refund_list");
        $refund_online_ticket = ((int) $userRole === 1) || $rolepermissionLibrary->create("refund_online_ticket");
        $cancel_create_office = ((int) $userRole === 1) || $rolepermissionLibrary->create("cancel_list");
        $cancel_create_online = ((int) $userRole === 1) || $rolepermissionLibrary->create("cancel_online_ticket");

        // DataTables request values
        $draw   = $request->getPost('draw');
        $start  = $request->getPost('start');
        $length = $request->getPost('length');
        $searchValue = $request->getPost('search')['value'] ?? '';

        // Column map
        $columns = [
            0 => 'tickets.id',
            1 => 'tickets.booking_id',
            2 => 'companies.name',
            3 => 'pick_location.name',
            4 => 'drop_location.name',
            5 => 'tickets.seatnumber',
            6 => null, // pick_stand (computed)
            7 => null, // drop_stand (computed)
            8 => 'user_details.first_name', // passenger_name (computed, use first_name for ordering)
            9 => 'tickets.payment_status',
            10 => 'tickets.journeydata',
            11 => 'tickets.trip_id',
            12 => null, // booked_by (computed)
            13 => null // action column
        ];

        // Order column & direction
        $order = $request->getPost('order');
        $orderColumnIndex = !empty($order) && isset($order[0]['column']) ? $order[0]['column'] : 0;
        $orderDirection   = !empty($order) && isset($order[0]['dir']) ? $order[0]['dir'] : 'desc';

        // ---- TOTAL RECORDS ----
        $countBuilder = $this->db->table('tickets');
        $countBuilder->select('tickets.id')
            ->where('tickets.cancel_status', 0)
            ->where('tickets.refund', 0)
            ->where('tickets.deleted_at IS NULL', null, false);

        $this->applyTicketServerListRoleFilter($countBuilder, $userRole, $userId, $company_id, $rolepermissionLibrary, ! in_array($userRole, [1], true));

        $totalRecords = $countBuilder->countAllResults(false);

        // ---- FILTERED QUERY ----
        $builder = $this->db->table('tickets');
        $builder->select(
            'tickets.id AS ticket_id, tickets.booking_id, tickets.subtrip_id, tickets.passanger_id, tickets.bookby_user_id, '
            .             'tickets.payment_status, tickets.journeydata, tickets.trip_id, tickets.seatnumber, tickets.pick_location_id, '
            . 'tickets.drop_location_id, tickets.pick_stand_id, tickets.drop_stand_id, tickets.bookby_user_type, '
            . 'CONCAT(COALESCE(NULLIF(TRIM(user_details.first_name), ""), passenger_agents.first_name, ""), " ", COALESCE(NULLIF(TRIM(user_details.last_name), ""), passenger_agents.last_name, "")) AS passengerName, '
            . 'CONCAT(COALESCE(booker.first_name, booker_agents.first_name, booker_agents_emp.first_name), " ", '
            . 'COALESCE(booker.last_name, booker_agents.last_name, booker_agents_emp.last_name)) AS booked_by, '
            . 'companies.name AS company_name, pick_location.name AS pick_location_name, drop_location.name AS drop_location_name',
            false
        )
            ->distinct()
            ->join("user_details","user_details.user_id = tickets.passanger_id", "left")
            ->join("agents AS passenger_agents","passenger_agents.user_id = tickets.passanger_id", "left")
            ->join("user_details AS booker","booker.user_id = tickets.bookby_user_id","left")
            ->join("agents AS booker_agents","booker_agents.user_id = tickets.bookby_user_id","left")
            ->join("users","users.id = tickets.bookby_user_id","left")
            ->join("employees AS booker_agents_emp","booker_agents_emp.email = users.login_email","left")
            ->join("trips","trips.id = tickets.trip_id","left")
            ->join("companies","trips.company_id = companies.id","left")
            ->join("locations as pick_location", "pick_location.id = tickets.pick_location_id", "left")
            ->join("locations as drop_location", "drop_location.id = tickets.drop_location_id", "left")
            ->where('tickets.cancel_status', 0)
            ->where('tickets.refund', 0)
            ->where('tickets.deleted_at IS NULL');

        // Apply role-based filtering
        $this->applyTicketServerListRoleFilter($builder, $userRole, $userId, $company_id, $rolepermissionLibrary, false);

        // Apply search filter
        if (!empty($searchValue)) {
            $searchDate = \App\Libraries\TripJourneyDateHelper::parseSearchDate($searchValue);
            if ($searchDate !== null) {
                (new \App\Libraries\TripJourneyDateHelper())->applyTicketBoardingDateSearch($builder, $searchDate);
            } else {
                $builder->groupStart()
                    ->like('tickets.booking_id', $searchValue)
                    ->orLike('companies.name', $searchValue)
                    ->orLike('pick_location.name', $searchValue)
                    ->orLike('drop_location.name', $searchValue)
                    ->orLike('user_details.first_name', $searchValue)
                    ->orLike('user_details.last_name', $searchValue)
                    ->orLike('tickets.payment_status', $searchValue)
                    ->orLike('tickets.seatnumber', $searchValue)
                    ->orLike('tickets.trip_id', $searchValue)
                    ->groupEnd();
            }
        }

        // Create a separate builder for filtered count
        $filteredCountBuilder = $this->db->table('tickets');
        $filteredCountBuilder->select('tickets.id')
            ->distinct()
            ->where('tickets.cancel_status', 0)
            ->where('tickets.refund', 0)
            ->where('tickets.deleted_at IS NULL', null, false);

        $needsSearchJoins = ! empty($searchValue) && \App\Libraries\TripJourneyDateHelper::parseSearchDate($searchValue) === null;
        if ($needsSearchJoins) {
            $filteredCountBuilder
                ->join('user_details', 'user_details.user_id = tickets.passanger_id', 'left')
                ->join('agents AS passenger_agents', 'passenger_agents.user_id = tickets.passanger_id', 'left')
                ->join('trips', 'trips.id = tickets.trip_id', 'left')
                ->join('companies', 'trips.company_id = companies.id', 'left')
                ->join('locations as pick_location', 'pick_location.id = tickets.pick_location_id', 'left')
                ->join('locations as drop_location', 'drop_location.id = tickets.drop_location_id', 'left');
        }

        $this->applyTicketServerListRoleFilter(
            $filteredCountBuilder,
            $userRole,
            $userId,
            $company_id,
            $rolepermissionLibrary,
            ! $needsSearchJoins && ! in_array($userRole, [1], true)
        );

        // Apply search filter for filtered count
        if (!empty($searchValue)) {
            $searchDate = \App\Libraries\TripJourneyDateHelper::parseSearchDate($searchValue);
            if ($searchDate !== null) {
                (new \App\Libraries\TripJourneyDateHelper())->applyTicketBoardingDateSearch($filteredCountBuilder, $searchDate);
            } else {
                $filteredCountBuilder->groupStart()
                    ->like('tickets.booking_id', $searchValue)
                    ->orLike('companies.name', $searchValue)
                    ->orLike('pick_location.name', $searchValue)
                    ->orLike('drop_location.name', $searchValue)
                    ->orLike('user_details.first_name', $searchValue)
                    ->orLike('user_details.last_name', $searchValue)
                    ->orLike('tickets.payment_status', $searchValue)
                    ->orLike('tickets.seatnumber', $searchValue)
                    ->orLike('tickets.trip_id', $searchValue)
                    ->groupEnd();
            }
        }

        // ---- TOTAL FILTERED ----
        $totalRecordwithFilter = $filteredCountBuilder->countAllResults(false);

        // ---- ORDERING ----
        if (isset($columns[$orderColumnIndex]) && $columns[$orderColumnIndex] !== null) {
            $builder->orderBy($columns[$orderColumnIndex], $orderDirection);
        } else {
            $builder->orderBy('tickets.id', 'desc');
        }

        // ---- PAGINATION ----
        if ($length > 0) {
            $builder->limit($length, $start);
        }

        // ---- FETCH RECORDS ----
        $records = $builder->get()->getResult();

        $journeyDateHelper = new \App\Libraries\TripJourneyDateHelper();
        $journeyDateHelper->warmBoardingDateCache($records);

        $bookingIds = [];
        foreach ($records as $row) {
            if (!empty($row->booking_id)) {
                $bookingIds[] = (string) $row->booking_id;
            }
        }
        $manifestByBooking = $journeyDateHelper->batchManifestDatesByBookingIds($bookingIds);

        // Load only locations / stands referenced on this page
        $locationIds = [];
        $standIds    = [];
        foreach ($records as $row) {
            if (!empty($row->pick_location_id)) {
                $locationIds[] = (int) $row->pick_location_id;
            }
            if (!empty($row->drop_location_id)) {
                $locationIds[] = (int) $row->drop_location_id;
            }
            if (!empty($row->pick_stand_id)) {
                $standIds[] = (int) $row->pick_stand_id;
            }
            if (!empty($row->drop_stand_id)) {
                $standIds[] = (int) $row->drop_stand_id;
            }
        }
        $locationIds = array_values(array_unique(array_filter($locationIds)));
        $standIds    = array_values(array_unique(array_filter($standIds)));

        $locationMap = [];
        if (!empty($locationIds)) {
            foreach ($this->locationModel->whereIn('id', $locationIds)->withDeleted()->findAll() as $loc) {
                $locationMap[$loc->id] = $loc->name;
            }
        }

        $standMap = [];
        if (!empty($standIds)) {
            foreach ($this->picdropModel
                ->select('pickdrops.id, pickdrops.time, stands.name as stand_name')
                ->join('stands', 'stands.id = pickdrops.stand_id')
                ->whereIn('pickdrops.id', $standIds)
                ->withDeleted()
                ->findAll() as $pd) {
                $standMap[$pd->id] = [
                    'name' => $pd->stand_name,
                    'time' => $pd->time ?? '',
                ];
            }
        }

        // ---- FORMAT JSON ----
        $data = [];
        $i = $start + 1;

        foreach ($records as $row) {
            // Get pick and drop location names
            $pickLocName = $locationMap[$row->pick_location_id] ?? '';
            $dropLocName = $locationMap[$row->drop_location_id] ?? '';

            // Get pick and drop stand names
            $pickStandHtml = '';
            if (isset($row->pick_stand_id) && isset($standMap[$row->pick_stand_id])) {
                $pickStand = $standMap[$row->pick_stand_id];
                $pickStandHtml = esc($pickStand['name']) . '<br/><small> ( ' . esc($pickStand['time']) . ' )</small>';
            }

            $dropStandHtml = '';
            if (isset($row->drop_stand_id) && isset($standMap[$row->drop_stand_id])) {
                $dropStand = $standMap[$row->drop_stand_id];
                $dropStandHtml = esc($dropStand['name']) . '<br/><small> ( ' . esc($dropStand['time']) . ' )</small>';
            }

            // Format payment status
            $paymentStatusHtml = '';
            if ($row->payment_status == "unpaid") {
                $paymentStatusHtml = lang("Localize.unpaid");
            } elseif ($row->payment_status == "paid") {
                $paymentStatusHtml = lang("Localize.paid");
            } else {
                $paymentStatusHtml = lang("Localize.partial");
            }

            // Format booked by
            $bookedByHtml = '';
            if (isset($row->bookby_user_type)) {
                if ($row->bookby_user_type == "passanger") {
                    $bookedByHtml = "online";
                } else {
                    $bookedByHtml = $row->bookby_user_type . "(" . ($row->booked_by ?? '') . ")";
                }
            }

            // Format journey date (ticket/manifest day per midnight rules)
            $manifestDate = $manifestByBooking[$row->booking_id] ?? null;
            $displayJourneyDate = $journeyDateHelper->getPassengerBoardingDateForDisplay(
                (int) $row->trip_id,
                (int) ($row->subtrip_id ?? 0),
                $row->journeydata,
                (int) $row->pick_stand_id,
                $manifestDate
            );

            $rowData = [
                "index" => $i++,
                "booking_id" => esc($row->booking_id),
                "company_name" => esc($row->company_name ?? ''),
                "pick_location" => esc($pickLocName),
                "drop_location" => esc($dropLocName),
                "seatnumber" => esc($row->seatnumber ?? ''),
                "pick_stand" => $pickStandHtml,
                "drop_stand" => $dropStandHtml,
                "passengerName" => esc($row->passengerName ?? ''),
                "payment_status" => $paymentStatusHtml,
                "journeydata" => $displayJourneyDate,
                "trip_id" => esc($row->trip_id ?? ''),
                "booked_by" => esc($bookedByHtml),
                "action" => $this->generateActionButtons($row, $cancel_create_office, $cancel_create_online, $refund_create, $refund_online_ticket)
            ];

            $data[] = $rowData;
        }

        return $this->response->setJSON([
            "draw" => intval($draw),
            "recordsTotal" => $totalRecords,
            "recordsFiltered" => $totalRecordwithFilter,
            "data" => $data
        ]);
    }

    /**
     * Matches ticket list column bookedBy: "online" when bookby_user_type is passenger (website/app).
     */
    private function ticketRowIsBookedOnline(object $row): bool
    {
        return isset($row->bookby_user_type) && (string) $row->bookby_user_type === 'passanger';
    }

    private function generateActionButtons($row, $cancel_create_office, $cancel_create_online, $refund_create, $refund_online_ticket)
    {
        $security = \Config\Services::security();
        $csrfToken = $security->getTokenName();
        $csrfHash = $security->getHash();

        $userRole = (int) session()->get('role_id');
        $isOnlineBooking = $this->ticketRowIsBookedOnline($row);

        $actionHtml = '';

        $showCancel = $isOnlineBooking ? $cancel_create_online : $cancel_create_office;
        if ($showCancel) {
            $actionHtml .= '<a href="' . base_url(route_to('new-cancel', $row->ticket_id, "ticket")) . '" class="btn btn-sm btn-info text-white"><i class="far fa-times-circle"></i> ' . lang("Localize.cancel") . '</a> ';
        }

        // Payment buttons
        if (($row->payment_status == "unpaid") || ($row->payment_status == "partial")) {
            $actionHtml .= '<a href="' . base_url(route_to('new-partialpaid', $row->ticket_id)) . '" class="btn btn-sm btn-primary text-white"><i class="far fa-money-bill-alt"></i> ' . lang("Localize.make") . ' ' . lang("Localize.payment") . ' </a> ';
        }

        if ($row->payment_status == "paid") {
            $actionHtml .= '<a href="' . base_url(route_to('detail-partialpaid', $row->booking_id)) . '" class="btn btn-sm btn-primary "><i class="fas fa-th-list"></i> ' . lang("Localize.payment") . ' ' . lang("Localize.details") . ' </a> ';
        }

        // Invoice button
        $actionHtml .= '<a href="' . base_url(route_to('show-ticketinvoice', $row->booking_id)) . '" class="btn btn-sm btn-success text-white"><i class="fas fa-file-invoice-dollar"></i> ' . lang("Localize.invoice") . ' </a> ';

        // Print button
        $actionHtml .= '<a target="_blank" href="' . base_url(route_to('print-ticketinvoice', $row->booking_id)) . '" class="btn btn-dark btn-sm"><i class="fas fa-print"></i></a> ';

        // Refund: same button; online rows (bookedBy = online) → manual online refund URL + permissions
        if ($row->payment_status != 'unpaid') {
            if ($isOnlineBooking) {
                if ($userRole === 1 || $refund_online_ticket) {
                    $refundUrl = base_url(route_to('new-online-manual-refund', $row->ticket_id));
                    $actionHtml .= '<a href="' . $refundUrl . '" class="btn btn-sm btn-warning text-white"><i class="fas fa-money-bill-wave"></i> ' . lang('Localize.refund') . ' </a>';
                }
            } elseif ($refund_create) {
                $refundUrl = base_url(route_to('new-refund', $row->ticket_id, 'ticket'));
                $actionHtml .= '<a href="' . $refundUrl . '" class="btn btn-sm btn-warning text-white"><i class="fas fa-money-bill-wave"></i> ' . lang('Localize.refund') . ' </a>';
            }
        }

        return $actionHtml;
    }

    /**
     * Parse session company_id (single id, comma-separated, or array).
     *
     * @return int[]
     */
    private function normalizeSessionCompanyIds($companyId): array
    {
        if ($companyId === null || $companyId === '' || $companyId === 0 || $companyId === '0') {
            return [];
        }

        if (is_array($companyId)) {
            $parts = $companyId;
        } else {
            $parts = preg_split('/\s*,\s*/', (string) $companyId, -1, PREG_SPLIT_NO_EMPTY);
        }

        return array_values(array_unique(array_filter(array_map('intval', $parts))));
    }

    /**
     * Restrict pre-trip list queries by role (requires trips join on builder).
     */
    private function applyPretripListScope($builder, $roleId, $companyId): void
    {
        if ((int) $roleId === 1) {
            return;
        }

        $companyIds = $this->normalizeSessionCompanyIds($companyId);
        if ($companyIds === []) {
            $builder->where('trips.company_id', 0);

            return;
        }

        if (count($companyIds) === 1) {
            $builder->where('trips.company_id', $companyIds[0]);
        } else {
            $builder->whereIn('trips.company_id', $companyIds);
        }
    }
    
        public function serverListPretrip()
    {
        $request = service('request');

        // Role and company ID from session
        $roleId = session()->get('role_id');
        $companyId = session()->get('company_id');

        // DataTables request values
        $draw   = $request->getPost('draw');
        $start  = $request->getPost('start');
        $length = $request->getPost('length');
        $searchValue = $request->getPost('search')['value'] ?? '';

        // Column map
        $columns = [
            0 => null, // index
            1 => 'companies.name', // company
            2 => 'pretrip.pretrip_id', // pre-trip no
            3 => 'vehicles.reg_no', // bus plate no
            4 => 'vehicles.bus_plate_no', // bus no
            5 => 'pretrip.bus_meter', // bus start meter
            6 => 'pretrip.trip_id', // trip
            7 => 'pic_location.name', // pick up
            8 => 'drop_location.name', // drop
            9 => 'pretrip.create_at', // trip date
            10 => 'schedules.start_time', // time
            11 => null, // driver (computed)
            12 => null, // assistance (computed)
            13 => 'vehicles.reg_no', // bus plate number
            14 => null, // created by (computed)
            15 => 'pretrip.Number_of_Seats', // seat
            16 => 'pretrip.notes', // note
            17 => 'pretrip.updated_at', // created date
            18 => null // action column
        ];

        // Order column & direction
        $order = $request->getPost('order');
        $orderColumnIndex = !empty($order) && isset($order[0]['column']) ? $order[0]['column'] : 17;
        $orderDirection   = !empty($order) && isset($order[0]['dir']) ? $order[0]['dir'] : 'desc';

        // ---- TOTAL RECORDS ----
        $countBuilder = $this->db->table('pretrip');
        $countBuilder->select('pretrip.id')
            ->join('trips', 'pretrip.trip_id = trips.id', 'left')
            ->join('companies', 'companies.id = trips.company_id', 'left');

        $this->applyPretripListScope($countBuilder, $roleId, $companyId);

        $countBuilder->where('pretrip.deleted_at IS NULL');

        $totalRecords = $countBuilder->countAllResults(false);

        // ---- FILTERED QUERY ----
        $builder = $this->db->table('pretrip');
        $builder->select('
                pretrip.id as pretripId,
                pretrip.pretrip_id,
                pretrip.bus_id,
                pretrip.trip_id,
                pretrip.Driver_id,
                pretrip.Assistant_id,
                pretrip.bus_meter,
                pretrip.Number_of_Seats,
                pretrip.notes,
                pretrip.create_at as pretrip_create,
                pretrip.updated_at as updatedAt,
                pretrip.created_by,
                trips.company_id,
                CONCAT(driver.first_name, " ", COALESCE(driver.last_name, "")) as driverName,
                CONCAT(COALESCE(assistance.first_name, ""), " ", COALESCE(assistance.last_name, "")) as assistanceName,
                vehicles.reg_no,
                vehicles.bus_plate_no,
                schedules.start_time as schedule_time,
                pic_location.name as pic_location_name,
                drop_location.name as drop_location_name,
                companies.name as company,
                CONCAT(COALESCE(booker.first_name, booker_agents.first_name, booker_agents_emp.first_name), " ", COALESCE(booker.last_name, booker_agents.last_name, booker_agents_emp.last_name)) AS createdBy
            ')
            ->join('trips', 'pretrip.trip_id = trips.id', 'left')
            ->join('companies', 'companies.id = trips.company_id', 'left')
            ->join('employees AS driver', 'pretrip.Driver_id = driver.id', 'left')
            ->join('employees AS assistance', 'pretrip.Assistant_id = assistance.id', 'left')
            ->join('vehicles', 'pretrip.bus_id = vehicles.id', 'left')
            ->join('schedules', 'trips.schedule_id = schedules.id', 'left')
            ->join('locations as pic_location', 'pic_location.id = trips.pick_location_id', 'left')
            ->join('locations as drop_location', 'drop_location.id = trips.drop_location_id', 'left')
            ->join('user_details as booker', 'pretrip.created_by = booker.user_id', 'left')
            ->join('agents as booker_agents', 'pretrip.created_by = booker_agents.user_id', 'left')
            ->join('users', 'users.id = pretrip.created_by', 'left')
            ->join('employees AS booker_agents_emp', 'booker_agents_emp.email = users.login_email', 'left');

        $this->applyPretripListScope($builder, $roleId, $companyId);

        $builder->where('pretrip.deleted_at IS NULL');

        // Global search
        if (!empty($searchValue)) {
            $builder->groupStart()
                ->like('companies.name', $searchValue)
                ->orLike('pretrip.pretrip_id', $searchValue)
                ->orLike('vehicles.reg_no', $searchValue)
                ->orLike('vehicles.bus_plate_no', $searchValue)
                ->orLike('pretrip.bus_meter', $searchValue)
                ->orLike('pretrip.trip_id', $searchValue)
                ->orLike('pic_location.name', $searchValue)
                ->orLike('drop_location.name', $searchValue)
                ->orLike('schedules.start_time', $searchValue)
                ->orLike('driver.first_name', $searchValue)
                ->orLike('driver.last_name', $searchValue)
                ->orLike('assistance.first_name', $searchValue)
                ->orLike('assistance.last_name', $searchValue)
                ->orLike('pretrip.Number_of_Seats', $searchValue)
                ->orLike('pretrip.notes', $searchValue)
                ->orLike('booker.first_name', $searchValue)
                ->orLike('booker.last_name', $searchValue)
                ->groupEnd();
        }

        // Create a separate builder for filtered count
        $filteredCountBuilder = $this->db->table('pretrip');
        $filteredCountBuilder->select('pretrip.id')
            ->join('trips', 'pretrip.trip_id = trips.id', 'left')
            ->join('companies', 'companies.id = trips.company_id', 'left')
            ->join('employees AS driver', 'pretrip.Driver_id = driver.id', 'left')
            ->join('employees AS assistance', 'pretrip.Assistant_id = assistance.id', 'left')
            ->join('vehicles', 'pretrip.bus_id = vehicles.id', 'left')
            ->join('schedules', 'trips.schedule_id = schedules.id', 'left')
            ->join('locations as pic_location', 'pic_location.id = trips.pick_location_id', 'left')
            ->join('locations as drop_location', 'drop_location.id = trips.drop_location_id', 'left')
            ->join('user_details as booker', 'pretrip.created_by = booker.user_id', 'left')
            ->join('agents as booker_agents', 'pretrip.created_by = booker_agents.user_id', 'left')
            ->join('users', 'users.id = pretrip.created_by', 'left')
            ->join('employees AS booker_agents_emp', 'booker_agents_emp.email = users.login_email', 'left');

        $this->applyPretripListScope($filteredCountBuilder, $roleId, $companyId);

        $filteredCountBuilder->where('pretrip.deleted_at IS NULL');

        // Apply search filter for filtered count
        if (!empty($searchValue)) {
            $filteredCountBuilder->groupStart()
                ->like('companies.name', $searchValue)
                ->orLike('pretrip.pretrip_id', $searchValue)
                ->orLike('vehicles.reg_no', $searchValue)
                ->orLike('vehicles.bus_plate_no', $searchValue)
                ->orLike('pretrip.bus_meter', $searchValue)
                ->orLike('pretrip.trip_id', $searchValue)
                ->orLike('pic_location.name', $searchValue)
                ->orLike('drop_location.name', $searchValue)
                ->orLike('schedules.start_time', $searchValue)
                ->orLike('driver.first_name', $searchValue)
                ->orLike('driver.last_name', $searchValue)
                ->orLike('assistance.first_name', $searchValue)
                ->orLike('assistance.last_name', $searchValue)
                ->orLike('pretrip.Number_of_Seats', $searchValue)
                ->orLike('pretrip.notes', $searchValue)
                ->orLike('booker.first_name', $searchValue)
                ->orLike('booker.last_name', $searchValue)
                ->groupEnd();
        }

        // ---- TOTAL FILTERED ----
        $totalRecordwithFilter = $filteredCountBuilder->countAllResults(false);

        // ---- ORDERING ----
        if (isset($columns[$orderColumnIndex]) && $columns[$orderColumnIndex] !== null) {
            $builder->orderBy($columns[$orderColumnIndex], $orderDirection);
        } else {
            $builder->orderBy('pretrip.updated_at', 'desc');
        }

        // ---- PAGINATION ----
        if ($length > 0) {
            $builder->limit($length, $start);
        }

        // ---- FETCH RECORDS ----
        $records = $builder->get()->getResult();

        // ---- FORMAT JSON ----
        $data = [];
        $i = $start + 1;

        foreach ($records as $row) {
            // Check if delete button should be shown (only if scheduled time is in future)
            $scheduledDateTimeStr = date('Y-m-d', strtotime($row->pretrip_create)) . ' ' . $row->schedule_time;
            $scheduledDateTime = strtotime($scheduledDateTimeStr);
            $currentDateTime = time();
            $canDelete = ($scheduledDateTime > $currentDateTime);

            $data[] = [
                "index" => $i++,
                "company" => esc($row->company ?? ''),
                "pretrip_id" => esc($row->pretrip_id ?? ''),
                "reg_no" => esc($row->reg_no ?? ''),
                "bus_plate_no" => "000" . esc($row->bus_plate_no ?? ''),
                "bus_meter" => esc($row->bus_meter ?? ''),
                "trip_id" => esc($row->trip_id ?? ''),
                "pic_location_name" => esc($row->pic_location_name ?? ''),
                "drop_location_name" => esc($row->drop_location_name ?? ''),
                "trip_date" => date("Y-m-d", strtotime($row->pretrip_create)),
                "schedule_time" => esc($row->schedule_time ?? ''),
                "driverName" => esc($row->driverName ?? ''),
                "assistanceName" => esc($row->assistanceName ?? ''),
                "bus_plate_number" => esc($row->reg_no ?? ''),
                "createdBy" => esc($row->createdBy ?? ''),
                "Number_of_Seats" => esc($row->Number_of_Seats ?? ''),
                "notes" => esc($row->notes ?? ''),
                "updatedAt" => date("Y-m-d H:i", strtotime($row->updatedAt)),
                "action" => $this->generatePretripActionButtons($row->pretripId, $canDelete)
            ];
        }

        return $this->response->setJSON([
            "draw" => intval($draw),
            "recordsTotal" => $totalRecords,
            "recordsFiltered" => $totalRecordwithFilter,
            "data" => $data
        ]);
    }

    private function generatePretripActionButtons($pretripId, $canDelete)
    {
        $actionHtml = '';
        
        // Print button (always shown)
        $actionHtml .= '<a target="_blank" href="' . base_url(route_to('print-pretrip', $pretripId)) . '" class="btn btn-sm btn-success text-white" title="' . lang("Localize.print") . '"><i class="fas fa-print"></i></a>';
        
        // Delete button (only if scheduled time is in future)
        if ($canDelete) {
            $actionHtml .= ' <a href="' . base_url(route_to('delete-pretrip', $pretripId)) . '" class="btn btn-sm btn-danger text-white" title="' . lang("Localize.delete") . '"><i class="fas fa-trash-alt"></i></a>';
        }
        
        return $actionHtml;
    }
    

    /**
     * Create 3 scheduled notifications for a ticket (2h / 1h / 30m before boarding time).
     *
     * @param string $bookingId Booking ID
     * @param int $passengerId Passenger user ID
     * @param int $tripId Trip ID
     * @param int $subtripId Subtrip ID
     * @param string $journeyDate Journey date
     */
    private function createTicketNotifications($bookingId, $passengerId, $tripId, $subtripId, $journeyDate)
    {
        try {
            $passengerDetail = $this->userDetailModel->where('user_id', $passengerId)->first();
            $passengerName = $passengerDetail
                ? trim(($passengerDetail->first_name ?? '') . ' ' . ($passengerDetail->last_name ?? ''))
                : 'Passenger';
            if ($passengerName === '') {
                $passengerName = 'Passenger';
            }

            $trip = $this->tripModel->find($tripId);
            if (! $trip) {
                return;
            }

            $subtrip = $this->subtripModel->find($subtripId);
            if (! $subtrip) {
                return;
            }

            $schedule = $this->scheduleeModel->find($trip->schedule_id);
            $scheduleStartTime = $schedule ? (string) ($schedule->start_time ?? '') : '';

            $ticket = $this->ticketModel->where('booking_id', $bookingId)->first();

            // Boarding point time + stand (pick_stand_id stores pickdrops.id)
            $boardingTime = $scheduleStartTime;
            $pickupStandName = 'Pickup location';
            $dropStandName = 'Drop location';

            $pickLocation = $this->locationModel->find($subtrip->pick_location_id ?? $trip->pick_location_id);
            if ($pickLocation) {
                $pickupStandName = (string) ($pickLocation->name ?? $pickupStandName);
            }
            $dropLocation = $this->locationModel->find($subtrip->drop_location_id ?? $trip->drop_location_id);
            if ($dropLocation) {
                $dropStandName = (string) ($dropLocation->name ?? $dropStandName);
            }

            if ($ticket && ! empty($ticket->pick_stand_id)) {
                $pickdrop = $this->picdropModel
                    ->select('pickdrops.time, stands.name as stand_name')
                    ->join('stands', 'stands.id = pickdrops.stand_id', 'left')
                    ->where('pickdrops.id', (int) $ticket->pick_stand_id)
                    ->withDeleted()
                    ->first();
                if ($pickdrop) {
                    if (! empty($pickdrop->time)) {
                        $boardingTime = (string) $pickdrop->time;
                    }
                    if (! empty($pickdrop->stand_name)) {
                        $pickupStandName = (string) $pickdrop->stand_name;
                    }
                }
            }

            if ($ticket && ! empty($ticket->drop_stand_id)) {
                $dropPickdrop = $this->picdropModel
                    ->select('stands.name as stand_name')
                    ->join('stands', 'stands.id = pickdrops.stand_id', 'left')
                    ->where('pickdrops.id', (int) $ticket->drop_stand_id)
                    ->withDeleted()
                    ->first();
                if ($dropPickdrop && ! empty($dropPickdrop->stand_name)) {
                    $dropStandName = (string) $dropPickdrop->stand_name;
                }
            }

            $driverName = 'Not assigned';
            $driverMobile = 'N/A';
            $driverAssignment = $this->stuffassignModel
                ->where('trip_id', $tripId)
                ->where('employee_type', '1')
                ->first();
            if ($driverAssignment) {
                $driver = $this->employeeModel->find($driverAssignment->employee_id);
                if ($driver) {
                    $driverName = trim(($driver->first_name ?? '') . ' ' . ($driver->last_name ?? ''));
                    if ($driverName === '') {
                        $driverName = (string) ($driver->name ?? 'Not assigned');
                    }
                    $driverMobile = trim((string) ($driver->phone ?? ''));
                    if ($driverMobile === '') {
                        $driverMobile = 'N/A';
                    }
                }
            }

            $busType = 'N/A';
            $vehicleId = $trip->vehicle_id ?? ($ticket->vehicle_id ?? null);
            $vehicle = $vehicleId ? $this->vehicleModel->find($vehicleId) : null;
            if ($vehicle) {
                if (! empty($vehicle->bus_type)) {
                    $fleetType = $this->fleetTypeModel->find($vehicle->bus_type);
                    if ($fleetType && ! empty($fleetType->type)) {
                        $busType = (string) $fleetType->type;
                    }
                }
                if ($busType === 'N/A' && ! empty($vehicle->reg_no)) {
                    $busType = (string) $vehicle->reg_no;
                }
            }

            $journeyDateOnly = date('Y-m-d', strtotime((string) $journeyDate));
            $journeyDateTime = $journeyDateOnly . ' ' . $boardingTime;
            $tripDateTime = strtotime($journeyDateTime);
            if ($tripDateTime === false) {
                return;
            }

            $notificationTime1 = date('Y-m-d H:i:s', $tripDateTime - (2 * 60 * 60));
            $notificationTime2 = date('Y-m-d H:i:s', $tripDateTime - (1 * 60 * 60));
            $notificationTime3 = date('Y-m-d H:i:s', $tripDateTime - (30 * 60));
            $currentTime = time();

            $templates = [
                [
                    'offset' => 2 * 60 * 60,
                    'when' => $notificationTime1,
                    'subject' => 'Your Trip is in 2 Hours! 🚌 | رحلتك بعد ساعتين! 🚌',
                    'message' => "Hi {$passengerName},\n"
                        . "Your ride is scheduled for {$boardingTime} today. Pickup at {$pickupStandName}\n"
                        . "Dropping at {$dropStandName}.\n"
                        . "Get ready and be at the pickup spot a few minutes early. We'll match you with your driver soon.\n"
                        . "Open the app for live updates!\n\n"
                        . "مرحباً {$passengerName}،\n"
                        . "رحلتك المحدده اليوم في {$boardingTime}.\n"
                        . "محطة الركوب {$pickupStandName}.\n"
                        . "محطه الوصول {$dropStandName}\n"
                        . "استعد وتواجد في محطة الركوب قبل الموعد ببضع دقائق. سنُعرّفك على سائقك قريباً.\n\n"
                        . "ابقي التطبيق مفتوح للاطلاع على آخر التحديثات!",
                ],
                [
                    'offset' => 1 * 60 * 60,
                    'when' => $notificationTime2,
                    'subject' => '1 Hour Until Your Ride! ⏰ | ⏰ !ساعة واحدة تفصلك عن رحلتك',
                    'message' => "Hello {$passengerName},\n"
                        . "Just a reminder: Your trip starts in 1 hour at {$boardingTime} from {$pickupStandName}.\n"
                        . "Your driver {$driverName}\n"
                        . "Mobile {$driverMobile}\n"
                        . "will be arriving in a {$busType}.\n\n"
                        . "مرحباً {$passengerName}،\n"
                        . "تذكير: تبدأ رحلتك بعد ساعة واحدة في تمام الساعة {$boardingTime} من {$pickupStandName}.\n"
                        . "سيصل سائقك {$driverName}\n"
                        . "رقم التواصل {$driverMobile}\n"
                        . "الباص {$busType}.",
                ],
                [
                    'offset' => 30 * 60,
                    'when' => $notificationTime3,
                    'subject' => 'Your Driver is on the Way – 30 Minutes to Go! 🚖 | سائقك في الطريق اليك - ٣٠ دقيقة متبقية! 🚌',
                    'message' => "Hey {$passengerName},\n"
                        . "Your ride is almost here! Pickup at {$boardingTime} ({$pickupStandName}).\n"
                        . "Driver: {$driverName}\n"
                        . "Mobile: {$driverMobile}\n"
                        . "| Vehicle: {$busType}\n"
                        . "Be ready outside – see you soon!\n\n"
                        . "مرحباً {$passengerName}،\n"
                        . "الباص على وشك الوصول! موعد الانطلاق {$boardingTime} ({$pickupStandName}).\n\n"
                        . "{$driverName} :السائق\n"
                        . "{$driverMobile} :رقم التواصل\n"
                        . "{$busType} :الباص\n"
                        . "كن مستعداً في الخارج - نراك قريباً!",
                ],
            ];

            foreach ($templates as $tpl) {
                if (($tripDateTime - (int) $tpl['offset']) <= $currentTime) {
                    continue;
                }

                $notificationId = $this->notificationModel->insert([
                    'subject' => $tpl['subject'],
                    'message' => $tpl['message'],
                    'notification_type' => 'scheduled',
                    'target_group' => 'specific_user',
                    'trip_id' => $tripId,
                    'journey_date' => $journeyDateOnly,
                    'notification_date' => $tpl['when'],
                    'repeat_option' => 'none',
                    'status' => 'scheduled',
                    'is_push_notification' => 1,
                    'created_by' => 0,
                ]);

                if ($notificationId) {
                    $this->userNotificationModel->insert([
                        'notification_id' => $notificationId,
                        'user_id' => $passengerId,
                        'is_read' => 0,
                    ]);
                }
            }
        } catch (\Exception $e) {
            log_message('error', 'Failed to create ticket notifications: ' . $e->getMessage());
        }
    }
    
    public function isAlreadybookedsubtrip($pickup, $drop, $subtripid, $busStartDate, $tripId, $ignoreToken = null, $userJourneyDate = null, $pickStandId = 0)
    {
        $journeyParam = $userJourneyDate !== null && $userJourneyDate !== ''
            ? (string) $userJourneyDate
            : (string) $busStartDate;

        return (new SegmentSeatInventoryService($this->db ?? \Config\Database::connect()))->collectUnavailableSeatNumbers(
            (int) $subtripid,
            $journeyParam,
            (int) $pickStandId,
            $ignoreToken,
            $pickup,
            $drop
        );
    }

    /**
     * @param list<string> $stoppageStops
     * @param array{bus_start: string, boarding: string, all: list<string>} $departureDates
     * @return list<string>
     */
    protected function getSegmentAwareAdminHeldSeats(
        int $tripId,
        int $subtripId,
        $pickup,
        $drop,
        array $stoppageStops,
        array $departureDates
    ): array {
        if ($tripId <= 0 || $subtripId <= 0 || empty($stoppageStops)) {
            return [];
        }

        if (! $this->canResolveSegment($stoppageStops, $pickup, $drop)) {
            return [];
        }

        $heldSeats = [];
        $inventoryService = new SegmentSeatInventoryService($this->db ?? \Config\Database::connect());
        foreach (HoldSeatSupport::fetchActiveHoldRowsForTripSubtrip($tripId, $subtripId, $departureDates['all']) as $row) {
            if (! $inventoryService->adminHoldRowOverlapsSegment($row, $stoppageStops, $pickup, $drop, $subtripId)) {
                continue;
            }
            foreach (array_filter(array_map('trim', explode(',', (string) ($row['seat_number'] ?? '')))) as $s) {
                if ($s !== '') {
                    $heldSeats[] = $s;
                }
            }
        }

        return array_values(array_unique($heldSeats));
    }

    /**
     * @param array<string,mixed> $holdRow
     */
    protected function adminHoldRowOverlapsSegment(
        array $holdRow,
        array $stops,
        $pickup,
        $drop,
        int $requestSubtripId
    ): bool {
        $subtripIds = array_values(array_filter(array_map('trim', explode(',', (string) ($holdRow['subtrip_id'] ?? '')))));
        if (empty($subtripIds)) {
            return false;
        }

        if (! in_array((string) $requestSubtripId, $subtripIds, true)) {
            return false;
        }

        $requestStoppage = '';
        $reqSub = $this->subtripModel->find($requestSubtripId);
        if ($reqSub) {
            $requestStoppage = trim((string) ($reqSub->stoppage ?? ''));
        }

        foreach ($subtripIds as $sid) {
            $sub = $this->subtripModel->find((int) $sid);
            if (! $sub) {
                continue;
            }
            if ($this->segmentsOverlapInStops(
                $stops,
                $pickup,
                $drop,
                $sub->pick_location_id,
                $sub->drop_location_id,
                $requestStoppage,
                trim((string) ($sub->stoppage ?? ''))
            )) {
                return true;
            }
        }

        return false;
    }

    public function getDynamicPickupDropCombinationsHold($tripId, $busStartDate, $boardingDate = null)
    {
        $this->db = \Config\Database::connect();
        $dates = BookingInventorySupport::resolveDepartureDates((int) $tripId, 0, (string) $busStartDate, $boardingDate);

        return BookingInventorySupport::flattenSeatNumbers(
            HoldSeatSupport::fetchActiveHoldRowsForTrip($tripId, $dates['all']),
            'seat_number'
        );
    }

    /**
     * Pick/drop rows for booking form — explicit columns so stands.* does not overwrite pickdrops.type.
     *
     * @return list<object>
     */
    private function loadPickdropsForBookingForm(int $subtripId, int $mainTripId): array
    {
        $select = 'pickdrops.id as pickdropid, pickdrops.trip_id, pickdrops.trip_type, pickdrops.stand_id, '
            . 'pickdrops.time, pickdrops.type as pickdrop_type, pickdrops.detail, stands.name as stand_name';

        $pickdrop = $this->picdropModel->select($select)
            ->join('stands', 'stands.id = pickdrops.stand_id')
            ->where('trip_id', $subtripId)
            ->orderBy('pickdrops.id', 'ASC')
            ->findAll();

        if (empty($pickdrop)) {
            $pickdrop = $this->picdropModel->select($select)
                ->join('stands', 'stands.id = pickdrops.stand_id')
                ->where('trip_id', $mainTripId)
                ->orderBy('pickdrops.id', 'ASC')
                ->findAll();
        }

        return $pickdrop;
    }

    private function applyTicketServerListRoleFilter(
        $builder,
        $userRole,
        $userId,
        $company_id,
        $rolepermissionLibrary,
        bool $ensureTripsJoin
    ): void {
        $userRole = (int) $userRole;

        if ($ensureTripsJoin) {
            $builder->join('trips', 'trips.id = tickets.trip_id', 'left');
        }

        if (! in_array($userRole, [1, 2, 7], true)) {
            if (! $rolepermissionLibrary->create('all_tickets')) {
                $builder->where('tickets.bookby_user_id', $userId);
            } else {
                $builder->where('trips.company_id', $company_id);
            }

            return;
        }

        if ($userRole === 7) {
            $builder->where('trips.company_id', $company_id);
        }

        if ($userRole === 2) {
            $builder->whereIn('trips.company_id', explode(',', (string) $company_id))
                ->where('tickets.bookby_user_id', $userId);
        }
    }
}
