<?php



namespace Modules\Ticket\Controllers\Api;



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 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\Company\Models\CompanyModel;

use Modules\User\Models\UserDetailModel;

use Modules\User\Models\UserModel;

use Modules\Ticket\Models\TemporaryBook;

use Modules\HoldSeat\Models\HoldSeatModel;

use Modules\Paymethod\Models\GeideaWave as ModelsGeideaWave;

use Modules\Ticket\Models\TemporaryBook as TemporaryBookModel;



use App\Libraries\Ticketmail;
use App\Libraries\HoldSeatSupport;
use App\Libraries\BookingInventorySupport;
use App\Libraries\TripJourneyDateHelper;
use App\Libraries\SegmentSeatAvailability;
use App\Libraries\SegmentSeatInventoryService;
use App\Libraries\TemporaryBookHoldService;
use App\Libraries\BookingOfferFareService;
use App\Libraries\BookingPassengerSupport;

use Modules\Paymethod\Models\StripeModel;

use Modules\Website\Models\WebsettingModel;

use Modules\Page\Models\PrivacyModel;
use Modules\Page\Models\TermsModel;



class Ticket extends BaseController

{

    use ResponseTrait;



    protected $Viewpath;

    protected $ticketModel;

    protected $tripModel;

    protected $companyModel;

    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 $stripeModel;

    protected $userModel;

    protected $userDetailModel;

    protected $journeylistModel;

    protected $partialpaidModel;

    protected $maxtimeModel;

    protected $webSettingModel;

    protected $geideaWaveModel;

    protected $TemporaryBook;

    protected $temporaryBookModel;

    protected $holdSeatModel;
    
    protected $termsModel;
    
    protected $secretKey = 'vikas';



    public function __construct()

    {

        $this->ticketModel = new TicketModel();

        $this->tripModel = new TripModel();

        $this->companyModel = new CompanyModel();

        $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->stripeModel = new StripeModel;



        $this->userModel = new UserModel();

        $this->userDetailModel = new UserDetailModel();



        $this->journeylistModel = new JourneylistModel();

        $this->partialpaidModel = new PartialpaidModel();



        $this->maxtimeModel = new MaxtimeModel();

        $this->webSettingModel = new WebsettingModel;

        $this->geideaWaveModel = new ModelsGeideaWave();

        $this->TemporaryBook = new TemporaryBook();

        $this->temporaryBookModel = new TemporaryBookModel;

        $this->holdSeatModel = new HoldSeatModel();
        
        $this->termsModel = new TermsModel();

    }







    public function bookticket()

    {

        $ticketmailLibrary = new Ticketmail();

        $ticketid = null;

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

        $rand = "TB" . $rand;



        $requestedJourneyDate = BookingInventorySupport::normalizeCalendarDate((string) $this->request->getVar('journeydate'));
        $journeyDateHelper    = new \App\Libraries\TripJourneyDateHelper();
        $pickStandId          = (int) $this->request->getVar('pickstand');
        $mainTripId           = (int) $this->request->getVar('trip_id');
        $subtripId            = (int) $this->request->getVar('subtripId');

        $overnightRun = new \App\Libraries\OvernightTripRunSupport();
        $pickstandMessage = $overnightRun->validatePickStandForSeatInventory($mainTripId, $subtripId, $pickStandId);
        if ($pickstandMessage !== null) {
            return $this->response->setJSON([
                'message' => $pickstandMessage,
                'status' => 'fail',
                'response' => 422,
                'data' => null,
            ]);
        }

        $bookingDates = BookingInventorySupport::resolveBookingStorageDates(
            $mainTripId,
            $subtripId,
            $requestedJourneyDate,
            $pickStandId,
            null,
            $this->resolveBoardingDateHintFromRequest()
        );
        $busStartDate = $bookingDates['bus_start'];
        $journeyDate  = $bookingDates['boarding'];






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

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

        $passengerResolution = $this->resolvePassengerForApi($login_email, $login_mobile);
        if (isset($passengerResolution['error'])) {
            return $this->response->setJSON($passengerResolution['error']);
        }
        $userid = $passengerResolution['user_id'];

        $this->db->transStart();













        $seatList = array_map('trim', array_filter(explode(',', (string) $this->request->getVar('seatnumbers'))));
        $clientDiscount = ($this->request->getVar('discount') !== null && $this->request->getVar('discount') !== '' && is_numeric($this->request->getVar('discount')))
            ? (float) $this->request->getVar('discount')
            : null;
        $fareCheck = $this->runOneWayOfferFareCheck(
            $mainTripId,
            $subtripId,
            $seatList,
            (string) ($this->request->getVar('offerer') ?? ''),
            (float) $this->request->getVar('grandtotal'),
            $clientDiscount,
            (float) ($this->request->getVar('tax') ?? 0)
        );
        if (isset($fareCheck['error'])) {
            return $this->response->setJSON($fareCheck['error']);
        }
        $bookingPrice = $this->request->getVar('totalprice');
        $bookingDiscount = $this->request->getVar('discount');
        $bookingTax = $this->request->getVar('tax');
        $bookingPaid = $this->request->getVar('grandtotal');
        if (! empty($fareCheck['expected'])) {
            $bookingPrice = $fareCheck['expected']['list_price'];
            $bookingDiscount = $fareCheck['expected']['offer_discount'];
            $bookingTax = $fareCheck['expected']['tax'];
            $bookingPaid = $fareCheck['expected']['grand_total'];
        }

                $ticketbooking = array(

            "booking_id" => $rand,

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

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

            "passanger_id" => $userid,

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

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

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

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

            "price" => $bookingPrice,

            "discount" => $bookingDiscount,

            "totaltax" => $bookingTax,

            "paidamount" => $bookingPaid,

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

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

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

            "refund" => 0,

            "bookby_user_id" => $userid,

            "bookby_user_type" => "passanger",

            "journeydata" => $journeyDate,

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

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

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

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

            "cancel_status" => 0,



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



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

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



        );



        $validTicketbooking = array(



            "booking_id" => $rand,

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

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

            "passanger_id" => $userid,



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

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

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

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



            "price" => $bookingPrice,

            "paidamount" => $bookingPaid,

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

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

            "bookby_user_id" => 1,

            "journeydata" => $journeyDate,



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

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



        );





        if ($this->validation->run($validTicketbooking, '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');

            }


            $this->db->transStart();

            $maitripid = (int) $this->request->getVar('trip_id');
            BookingInventorySupport::lockTripRowForUpdate($this->db, $maitripid);

            $subtripid = $this->request->getVar('subtripId');
            $piclocation = $this->request->getVar('pick_location_id');
            $droplocation = $this->request->getVar('drop_location_id');
            $ticket_token = $this->request->getVar('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();
                $seatBilingual = admin_bilingual_lang_strings('Localize.seat_already_booked_segment', ['seats' => implode(',', $intersection)]);
                return $this->response->setJSON([
                    'message' => $seatBilingual['en'],
                    'message_ar' => $seatBilingual['ar'],
                    'status' => "fail",
                    'response' => 409,
                    'data' => null
                ]);
            }

            $ticketid = $this->ticketModel->insert($ticketbooking);



            if (empty($ticketid)) {



                $data = [

                    'message' => "booking data error",

                    'status' => "success",

                    'response' => 204,

                    'data' => "booking data not appropriate",

                ];

                return $this->response->setJSON($data);

            }







            $partialPaid = array(

                "booking_id" => $rand,

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

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

                "passanger_id" => $userid,

                "paidamount" => $paidamount,



            );

            $paidpartial = array(

                "booking_id" => $rand,

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

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

                "passanger_id" => $userid,

                "paidamount" => $paidamount,

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

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

            );









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



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









                $maitripid = $this->request->getVar('trip_id');

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

                $piclocation = $this->request->getVar('pick_location_id');

                $droplocation = $this->request->getVar('drop_location_id');

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

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



                $manifestDate = $busStartDate;
                BookingInventorySupport::logBookingDateContext('bookticket_journeylist', [
                    'trip_id' => (int) $maitripid,
                    'subtrip_id' => (int) $subtripid,
                    'search_date' => $requestedJourneyDate,
                    'pickstand' => (int) $pick_stand_id,
                    'service_date' => $manifestDate,
                    'boarding_date' => $journeyDate,
                    'stored_ticket_date' => $journeyDate,
                    'stored_manifest_date' => $manifestDate,
                    'booking_id' => $rand,
                ]);
                $journeylist = $this->journeylist($rand, $userid, $maitripid, $subtripid, $piclocation, $droplocation, $pick_stand_id, $drop_stand_id, $manifestDate);



                if (empty($journeylist)) {

                    $data = [

                        'status' => "fail",

                        'response' => 204,

                        'data' => "journey list data not inserted",

                    ];

                }



                $this->db->transComplete();

                $ticketInfo = $this->ticketModel->find($ticketid);

                $data = [
                    'status' => 'success',
                    'response' => 200,
                    'data' => $ticketInfo,
                ];

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

            } else {



                $errors = $this->validation;

                $data = [

                    'message' => "Booking & Paid Information Not Valid",

                    'status' => "failed",

                    'response' => 204,

                    'errors' => $errors->listErrors(),

                ];

            }

        } else {



            $errors = $this->validation;

            $data = [

                'message' => "Booking Information Not Valid",

                'status' => "failed",

                'response' => 204,

                'errors' => $errors,

            ];

        }



        return $this->response->setJSON($data);

    }



    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');



        $newPassangerFName =  json_decode($newPassangerFName, true);

        $newPassangerLName =  json_decode($newPassangerLName, true);

        $newPassangerMobile =  json_decode($newPassangerMobile, true);

        $newPassangerNidNumber =  json_decode($newPassangerNidNumber, true);



        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],



                );

            }







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



            if (empty($alljourneydata)) {

                $data = [

                    'message' => "Multiple Pasanger input error",

                    'status' => "failed",

                    'response' => 204,

                    'data' => "journey list input error",

                ];

                return $this->response->setJSON($data);

            }

        }





        return   $joruneylistid;

    }





    public function userCheck($login_email, $login_mobile)

    {

        $userid = null;
        $login_email = trim((string) $login_email);
        $login_mobile = trim((string) $login_mobile);

        // For guest round-trip flows, keep booking unblocked by generating
        // fallback identity when frontend did not send email/mobile yet.
        if ($login_email === '' && $login_mobile === '') {
            $seed = preg_replace('/\D+/', '', (string) microtime(true));
            $login_email = 'guest+' . substr($seed, -10) . '@busvalley.local';
            $login_mobile = '9' . substr(str_pad($seed, 10, '0', STR_PAD_LEFT), -9);
        }

        $evalue = [];
        $mvalue = [];
        if ($login_email !== '') {
            $evalue = $this->userModel->withDeleted()->where('login_email', $login_email)->findAll();
        }
        if ($login_mobile !== '') {
            $mvalue = $this->userModel->withDeleted()->where('login_mobile', $login_mobile)->findAll();
        }



        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";





            $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);



                $validdata = array(

                    "user_id" => $userid,

                    "first_name" => $this->request->getVar('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'),

                );



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

                    $data = array(

                        "user_id" => $userid,

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

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

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

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

                        "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();

                }

            } else {
                // If creation validation fails (commonly unique mobile/email race),
                // try resolving an existing user again before failing.
                $existing = null;
                if ($login_mobile !== '') {
                    $existing = $this->userModel->where('login_mobile', $login_mobile)->first();
                }
                if (!$existing && $login_email !== '') {
                    $existing = $this->userModel->where('login_email', $login_email)->first();
                }
                if ($existing && !empty($existing->id)) {
                    return $existing->id;
                }
            }



            return $userid;

        }

    }



    public function busSeat($subTripId, $journeyDate)

    {
        $journeyDate = BookingInventorySupport::normalizeCalendarDate((string) $journeyDate);
        $pickContext = $this->resolveApiPickstandSeatContext((int) $subTripId, $journeyDate);
        if ($pickContext['awaiting_pickstand']) {
            return $this->buildDeferredPickstandSeatResponse($pickContext);
        }

        $inventory = $this->collectApiSeatInventory(
            (int) $subTripId,
            $journeyDate,
            null,
            (int) $pickContext['pick_stand_id']
        );
        $bookSeat = array_values(array_unique(array_merge(
            $inventory['booked'],
            $inventory['hold'],
            $inventory['block']
        )));

        $displaySeat = array();

        $sortingdisplaySeat = array();

        $anotherarray = array();

        $lastSeat = null;



        // sub trip and fllet details

        $subtripInfo = $this->subtripModel

            ->select('subtrips.*, trips.fleet_id')

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

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

            ->first();



        $totalseat = 0;

        $getFleetDetails = [];



        if(isset($subtripInfo->fleet_id) && !empty($subtripInfo->fleet_id)){



            $getFleetDetails = $this->fleetTypeModel->where('status', 1)->find($subtripInfo->fleet_id);

            // total seat

            $totalseat = (int) $getFleetDetails->total_seat + (int)$getFleetDetails->last_seat;



        }        

        



        // build layout

        $layout = [];

        $seatnumber = [];

        if(isset($getFleetDetails->layout) && !empty($getFleetDetails->layout)){

            $layout = explode("-", $getFleetDetails->layout);

            $seatnumber = explode(",", $getFleetDetails->seat_number);

        }

        

        $seatColumn = count($layout);

        $numberSeatRow = array_sum($layout);

        $storeSeatNumber = $seatnumber;





        if (isset($getFleetDetails->last_seat) && $getFleetDetails->last_seat) {

            $lastSeat = array_slice($seatnumber, -1, 1);

            array_pop($seatnumber);

        }

        $totalseatnumber = count($seatnumber);



        if($numberSeatRow > 0){

            $seatloopslicenumber =  floor($totalseatnumber / $numberSeatRow);

        }else{

            $seatloopslicenumber =  floor($totalseatnumber / 1);

        }

        



        for ($i = 1; $i <= $seatloopslicenumber; $i++) {

            $arrayslice = null;

            $arrayslice = array_splice($seatnumber, $numberSeatRow);

            $displaySeat[$i] = $seatnumber;

            $seatnumber  =  $arrayslice;

        }







        for ($totalseatrow = 1; $totalseatrow  <= $seatloopslicenumber; $totalseatrow++) {



            for ($column = 0; $column < $seatColumn; $column++) {

                $x = 0;

                foreach ($displaySeat[$totalseatrow] as $key => $seatvalue) {



                    if ($layout[$column] >= $key + 1) {

                        array_push($anotherarray, $seatvalue);

                    } else {

                        array_push($anotherarray, null);



                        break;

                    }

                    array_shift($displaySeat[$totalseatrow]);

                }

            }

            $sortingdisplaySeat[$totalseatrow] = $anotherarray;

            $anotherarray = array();

        }











        $kyepos = null;

        if (!empty($lastSeat)) {



            foreach ($sortingdisplaySeat[$seatloopslicenumber] as $key => $checknull) {

                if ($checknull == null) {

                    $sortingdisplaySeat[$seatloopslicenumber][$key] = $lastSeat[0];

                }

            }

        }







        $newseatarray = array();

        $arraynew = array();

        $id = 1;

        foreach ($sortingdisplaySeat as $key => $shortseat) {



            foreach ($shortseat as $skey => $newseat) {



                if ($newseat == null) {

                    array_push($newseatarray, null);

                } else {

                    if (in_array($newseat, $bookSeat)) {

                        $seatvalue = true;

                    } else {

                        $seatvalue = false;

                    }

                    $seatarray  = array(

                        "id" => $id,

                        "seatNumber" => $newseat,

                        "isReserved" => $seatvalue,

                    );

                    array_push($newseatarray, $seatarray);

                }





                $id = $id + 1;

            }



            $arraynew[] = $newseatarray;

            $newseatarray = array();

        }



        // print_r($arraynew);

        // exit;

        $data = [

            'status' => "success",

            'response' => 200,

            'requires_pickstand' => $pickContext['requires_pickstand'],

            'eligible_pick_stands' => $pickContext['eligible_pick_stands'],

            'layout' =>  $getFleetDetails->layout ?? [],

            'seatlayout' => $arraynew,

            'totalseat' => $totalseat,



        ];



        return $this->response->setJSON($data);

    }





    public function singelBooking($bookingid)

    {







        $ticket =  $this->ticketModel->where('booking_id', $bookingid)->first();
        
        $main_privacy_policy = $this->termsModel->first();



        if (empty($ticket)) {



            $data = [

                'message' => "No ticket Found",

                'status' => "fail",

                'response' => 201,

                'data' => $ticket,

            ];

        } else {

            $passengerdata = $this->userModel->withDeleted()->find($ticket->passanger_id);

            if ($passengerdata !== null) {
                $ticket->mobile = $passengerdata->login_mobile;
                $ticket->email = $passengerdata->login_email;
                $name = BookingPassengerSupport::passengerNameForUser(
                    $this->userModel,
                    $this->userDetailModel,
                    (int) $ticket->passanger_id,
                    (string) $ticket->booking_id
                );
                $ticket->fullName = trim($name['first_name'] . ' ' . $name['last_name']);
            } else {
                $ticket->mobile = '';
                $ticket->email = '';
                $ticket->fullName = '';
            }

            $company = null;
            if (! empty($ticket->vehicle_id)) {
                $company = $this->vehicleModel->withDeleted()->where('id', $ticket->vehicle_id)->first();
            }

            $ticket->company = $company !== null ? ($company->company ?? '') : '';



            $seatwithClass = [];

            $classData = [];

            $company_name = $this->tripModel->withDeleted()->where('id', $ticket->trip_id)->first();

            $this->db->transStart();

            $allSeatClass = $this->db->table('seat_class')->get()->getResult();

            $this->db->transComplete();

            foreach ($allSeatClass as $class) {

                $classData[$class->id] = $class;

            }



            $subtripInformation = $this->subtripModel->withDeleted()->find($ticket->subtrip_id);

            if ($subtripInformation && isset($subtripInformation->subtrip_seatclass) && ! empty($subtripInformation->subtrip_seatclass)) {
                $seatclass = $subtripInformation->subtrip_seatclass ?? [];
            } else {
                $seatclass = ($company_name && isset($company_name->seatclass)) ? $company_name->seatclass : [];
            }

            $seatclass = is_string($seatclass) ? json_decode($seatclass, true) : $seatclass;
            if (! is_array($seatclass)) {
                $seatclass = [];
            }



            if (!empty($ticket->seatnumber)) {

                $seatNumbers = explode(',', $ticket->seatnumber); // example: "1,2,3"



                foreach ($seatNumbers as $seat) {

                    $seat = trim($seat);

                    $classFound = null;



                    // Find seat class for this seat

                    foreach ($seatclass as $sc) {
                        if (! is_array($sc) || empty($sc['seatNo']) || ! is_array($sc['seatNo'])) {
                            continue;
                        }

                        if (in_array($seat, $sc['seatNo'], true)) {

                            $classFound = $sc['seatClass'] ?? null;

                            break;

                        }

                    }



                    $seatwithClass[] = [

                        "seatnumber" => $seat,

                        "seatclass"  => ($classFound !== null && isset($classData[$classFound])) ? $classData[$classFound]->name : 'Economy', // fallback if no match

                    ];

                }

            }





            $ticket->seatwithClass = $seatwithClass;
            $companyTerms = [];
            $companyName = null;
            if ($company_name) {
                $companyName = $this->companyModel->where('id', $company_name->company_id)->first();
                $this->db->transStart();
                $companyTerms = $this->db->table('company_terms')
                    ->where('company_id', $company_name->company_id)
                    ->get()
                    ->getRow();
                
                $this->db->transComplete();
            }



            // $ticket->company_name = $company_name->company_name;
            
            


            $ticket->company_name = $companyName !== null ? ($companyName->name ?? '') : '';

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

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

                                    ->where('pickdrops.id', $ticket->pick_stand_id)

                                    ->first();

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

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

                ->where('pickdrops.id', $ticket->drop_stand_id)

                ->first();
                
            $pickUpLocationName = $this->locationModel->withDeleted()->select('*')

                ->where('id', $ticket->pick_location_id)

                ->first();

            $pickDropLocationName = $this->locationModel->withDeleted()->select('*')

                ->where('id', $ticket->drop_location_id)

                ->first();

            $ticket->Pickupstand = $pickUpStandName !== null ? ($pickUpStandName->stand_name ?? '') : '';
            $ticket->PickDropstand = $pickDropStandName !== null ? ($pickDropStandName->stand_name ?? '') : '';
            $ticket->PickupLocation = $pickUpLocationName !== null ? ($pickUpLocationName->name ?? '') : '';
            $ticket->PickupLocationTime = $pickUpStandName !== null ? ($pickUpStandName->time ?? '') : '';
            $ticket->PickDropLocationTime = $pickDropStandName !== null ? ($pickDropStandName->time ?? '') : '';
            $ticket->PickDropLocation = $pickDropLocationName !== null ? ($pickDropLocationName->name ?? '') : '';
            $ticket->main_privacy_policy = $main_privacy_policy ?? '';
            $ticket->privacy_policy = ($companyTerms && isset($companyTerms->description)) ? $companyTerms->description : '';

            // Normalize journey date for display (ticket/manifest day; legacy boarding rows included).
            $journeyDateHelper = new \App\Libraries\TripJourneyDateHelper();
            $boardingDate = $journeyDateHelper->getPassengerBoardingDateForDisplay(
                (int) $ticket->trip_id,
                (int) $ticket->subtrip_id,
                $ticket->journeydata,
                (int) ($ticket->pick_stand_id ?? 0)
            );
            $ticket->journeydata = $boardingDate;
            $ticket->boarding_date = $boardingDate;
            
                        $facility = "";
            if ($company_name && ! empty($company_name->facility)) {
                $facilityIds = array_filter(explode(',', $company_name->facility));
                if (!empty($facilityIds)) {
                    $this->db->transStart();
                    $facilitys = $this->db->table('facilitys')
                        ->whereIn('id', $facilityIds)
                        ->get()
                        ->getResult();
                    $this->db->transComplete();
                    
                    if (!empty($facilitys)) {
                        $facilityNames = array_column($facilitys, 'name');
                        $facility = implode(", ", $facilityNames);
                    }
                }
            }
            $ticket->Facility = $facility;
            


            $data = [

                'message' => "Ticket found",

                'status' => "success",

                'response' => 200,

                'data' => $ticket,

            ];

        }



        return $this->response->setJSON($data);

    }







    public function paylaterByUser()

    {





        $bookingid = $this->request->getVar('booking_id');



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



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



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



        $ticketDetail =  $this->ticketModel->where('booking_id', $bookingid)->first();





        $tickeid =     $ticketDetail->id;

        $backUserId = $ticketDetail->passanger_id;

        $payment_detail_rocord = $paydetail;

        $amountToPaid = $ticketDetail->paidamount;

        $subtripid = $ticketDetail->subtrip_id;

        $maitripid = $ticketDetail->trip_id;

        $rand = $bookingid;





        if ($paidamount == $amountToPaid) {



            $validPaid = array(

                "booking_id" => $bookingid,

                "trip_id" => $maitripid,

                "subtrip_id" => $subtripid,

                "passanger_id" => $backUserId,

                "paidamount" => $paidamount,

                "pay_method_id" => $pay_type_id,

            );

            $paidpartial = array(

                "booking_id" => $bookingid,

                "trip_id" => $maitripid,

                "subtrip_id" => $subtripid,

                "passanger_id" => $backUserId,

                "paidamount" => $paidamount,

                "pay_method_id" => $pay_type_id,

                "payment_detail" => $payment_detail_rocord,

            );



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



                $this->db->transStart();



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



                $data = [

                    'id' => $tickeid,

                    'payment_status' => "paid",

                    "pay_method_id" => $pay_type_id,

                ];



                $this->ticketModel->save($data);





                $paymethod_id =  $pay_type_id;

                $payDetail = $payment_detail_rocord;

                $type = "income";

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

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

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



                $this->db->transComplete();



                $data = [

                    'message' => "Transaction success",

                    'status' => "success",

                    'response' => 200,



                ];

                return $this->response->setJSON($data);

            } else {



                $data = [

                    'message' => "Error in Validation",

                    'error' => $this->validation->getErrors(),

                    'status' => "fail",

                    'response' => 404,

                ];



                return $this->response->setJSON($data);

            }

        } else {



            $data = [

                'message' => "Error in amount",

                'status' => "fail",

                'response' => 404,

            ];



            return $this->response->setJSON($data);

        }

    }



    public function stripePayment()

    {

        $rules = [

            'stripetoken'  => 'required',

            'amount'       => 'required',

        ];



        if ($this->validate($rules)) {

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

            $paymentToken = $this->request->getVar('stripetoken');

            $getPayData = $this->stripeModel->first();



            if ($getPayData->environment == 1) {

                $secret_key = $getPayData->live_s_kye;

                $environment = "live";

            } else {

                $secret_key = $getPayData->test_s_kye;

                $environment = "Test";

            }



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

            $currencybuilder = $this->db->table('currencies');

            $curencyquery = $currencybuilder->where('id', $websetting->currency)->get();

            $currency = $curencyquery->getRow()->code;



            try {

                \Stripe\Stripe::setApiKey($secret_key);



                // stripe, old charge code

                /* $paymentIntent = \Stripe\Charge::create([

                    "amount"     => $amount * 100,

                    "currency"     => $currency,

                    "source"     => $paymentToken,

                    "description"   => "Seat Booking Payment"

                ]); */



                // upgrading to 3Ds

                $customer = \Stripe\Customer::create([

                    'name' => 'Jahid Limon',

                    'email' => 'jahid@bdtask.net'

                ]);



                $paymentIntent = \Stripe\PaymentIntent::create([

                    'amount' => $amount,

                    'currency' => $currency,

                    'payment_method_data' => [

                        'type' => 'card',

                        'card' => [

                            'token' => $paymentToken,

                        ],

                    ],

                    'confirmation_method' => 'manual',

                    'customer' => $customer->id

                ]);



                $paymentIntent->confirm();



                $data = [

                    'message'   => "Payment Successfull",

                    'status'    => "success",

                    'response'  => 200,

                    'data'      => $paymentIntent,

                ];

                return $this->response->setJSON($data);

            } catch (\Exception $e) {

                $data = [

                    'message' => "Payment Fail",

                    'status' => "fail",

                    'response' => 404,

                    'data' => $e->getMessage(),

                ];

                return $this->response->setJSON($data);

            }

        } else {

            $data = array(

                'success' => false,

                'response' => 204,

                'message' => 'All field required',

                'data' => $this->validator->getErrors(),

            );

            return $this->response->setJSON($data);

        }

    }



    public function laterBookticket()

    {

        $ticketmailLibrary = new Ticketmail();

        $ticketid = null;

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

        $rand = "TB" . $rand;



        $requestedJourneyDate = BookingInventorySupport::normalizeCalendarDate((string) $this->request->getVar('journeydate'));
        $journeyDateHelper    = new \App\Libraries\TripJourneyDateHelper();
        $pickStandId          = (int) $this->request->getVar('pickstand');
        $mainTripId           = (int) $this->request->getVar('trip_id');
        $subtripId            = (int) $this->request->getVar('subtripId');

        $overnightRun = new \App\Libraries\OvernightTripRunSupport();
        $pickstandMessage = $overnightRun->validatePickStandForSeatInventory($mainTripId, $subtripId, $pickStandId);
        if ($pickstandMessage !== null) {
            return $this->response->setJSON([
                'message' => $pickstandMessage,
                'status' => 'fail',
                'response' => 422,
                'data' => null,
            ]);
        }

        $bookingDates = BookingInventorySupport::resolveBookingStorageDates(
            $mainTripId,
            $subtripId,
            $requestedJourneyDate,
            $pickStandId,
            null,
            $this->resolveBoardingDateHintFromRequest()
        );
        $busStartDate = $bookingDates['bus_start'];
        $journeyDate  = $bookingDates['boarding'];





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

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

        $passengerResolution = $this->resolvePassengerForApi($login_email, $login_mobile);
        if (isset($passengerResolution['error'])) {
            return $this->response->setJSON($passengerResolution['error']);
        }
        $userid = $passengerResolution['user_id'];

        $this->db->transStart();













        $ticketbooking = array(

            "booking_id" => $rand,

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

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

            "passanger_id" => $userid,

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

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

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

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

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

            "discount" => $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'),

            "refund" => 0,

            "bookby_user_id" => $userid,

            "bookby_user_type" => "passanger",

            "journeydata" => $journeyDate,

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

            "pay_method_id" => 999,

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

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

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

            "cancel_status" => 0,

            "offerer" => 0,

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

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

        );

        $validTicketbooking = array(



            "booking_id" => $rand,

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

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

            "passanger_id" => $userid,



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

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

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

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



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

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

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

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

            "bookby_user_id" => 1,

            "journeydata" => $journeyDate,



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

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



        );





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





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



            if ($paymentStatus == "unpaid") {

                $paidamount = 0;

            }









                        $maitripidLater = (int) $this->request->getVar('trip_id');
            $subtripidLater = (int) $this->request->getVar('subtripId');
            BookingInventorySupport::lockTripRowForUpdate($this->db, $maitripidLater);

            $allBookedSeatsLater = $this->isAlreadybookedsubtrip(
                (int) $this->request->getVar('pick_location_id'),
                (int) $this->request->getVar('drop_location_id'),
                $subtripidLater,
                $busStartDate,
                $maitripidLater,
                null,
                $requestedJourneyDate,
                (int) $this->request->getVar('pickstand')
            );
            $requestedSeatsLater = array_map('trim', array_filter(explode(',', (string) $this->request->getVar('seatnumbers'))));
            $intersectionLater = array_intersect($requestedSeatsLater, $allBookedSeatsLater);
            if (! empty($intersectionLater)) {
                $this->db->transRollback();

                $seatBilingual = admin_bilingual_lang_strings('Localize.seat_already_booked_segment', ['seats' => implode(',', $intersectionLater)]);
                return $this->response->setJSON([
                    'message' => $seatBilingual['en'],
                    'message_ar' => $seatBilingual['ar'],
                    'status' => 'fail',
                    'response' => 409,
                    'data' => null,
                ]);
            }

$ticketid = $this->ticketModel->insert($ticketbooking);



            if (empty($ticketid)) {



                $data = [

                    'message' => "booking data error",

                    'status' => "success",

                    'response' => 204,

                    'data' => "booking data not appropriate",

                ];

                return $this->response->setJSON($data);

            }







            $partialPaid = array(

                "booking_id" => $rand,

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

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

                "passanger_id" => $userid,

                "paidamount" => $paidamount,



            );

            $paidpartial = array(

                "booking_id" => $rand,

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

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

                "passanger_id" => $userid,

                "paidamount" => $paidamount,

                "pay_method_id" => 999,

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

            );









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



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









                $maitripid = $this->request->getVar('trip_id');

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

                $piclocation = $this->request->getVar('pick_location_id');

                $droplocation = $this->request->getVar('drop_location_id');

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

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



                $manifestDate = $busStartDate;
                BookingInventorySupport::logBookingDateContext('bookticket_journeylist', [
                    'trip_id' => (int) $maitripid,
                    'subtrip_id' => (int) $subtripid,
                    'search_date' => $requestedJourneyDate,
                    'pickstand' => (int) $pick_stand_id,
                    'service_date' => $manifestDate,
                    'boarding_date' => $journeyDate,
                    'stored_ticket_date' => $journeyDate,
                    'stored_manifest_date' => $manifestDate,
                    'booking_id' => $rand,
                ]);
                $journeylist = $this->journeylist($rand, $userid, $maitripid, $subtripid, $piclocation, $droplocation, $pick_stand_id, $drop_stand_id, $manifestDate);



                if (empty($journeylist)) {

                    $data = [

                        'status' => "fail",

                        'response' => 204,

                        'data' => "journey list data not inserted",

                    ];

                }



                $this->db->transComplete();









                $ticketInfo =  $this->ticketModel->find($ticketid);









                $emaildata = $ticketmailLibrary->getticketEmailData($rand);

                try {
                    if (! empty($emaildata['ticket'])) {
                        $status = sendTicket($login_email, $emaildata);
                    } else {
                        $status = false;
                    }
                } catch (\Throwable $e) {
                    log_message('error', 'API bookticket email failed for {booking}: {message}', [
                        'booking' => $rand,
                        'message' => $e->getMessage(),
                    ]);
                    $status = false;
                }



                if ($status == true) {

                    $data = [

                        'status' => "success",

                        'response' => 200,

                        'data' => $ticketInfo,

                    ];

                } else {

                    $data = [

                        'status' => "success",

                        'response' => 200,

                        'data' => $ticketInfo,

                        'emailerror' => $status,

                    ];

                }

            } else {



                $errors = $this->validation;

                $data = [

                    'message' => "Booking & Paid Information Not Valid",

                    'status' => "failed",

                    'response' => 204,

                    'errors' => $errors->listErrors(),

                ];

            }

        } else {



            $errors = $this->validation->getErrors();

            $data = [

                'message' => "Booking Information Not Valid",

                'status' => "failed",

                'response' => 204,

                'errors' => $errors,

            ];

        }



        return $this->response->setJSON($data);

    }



    public function busSeatNew($subTripId, $journeyDate)

    {
        $pickContext = $this->resolveApiPickstandSeatContext((int) $subTripId, (string) $journeyDate);
        if ($pickContext['awaiting_pickstand']) {
            return $this->buildDeferredPickstandSeatResponse($pickContext);
        }

        $subTripId = $subTripId;

        $subtripInfo = $this->subtripModel

            ->select('subtrips.*, trips.fleet_id, trips.seatclass')

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

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

            ->first();

        if(!empty($subtripInfo)){

            $fleetId = $subtripInfo->fleet_id;

            $seatClass  = $subtripInfo->subtrip_seatclass;

            if(empty($seatClass)){

                $seatClass  = $subtripInfo->seatclass;

            }

            $seatData = [];

            if(empty($seatClass)){

                $seatData = json_decode($subtripInfo->subtrip_seatclass,true);

            } else {

                $seatData = json_decode($subtripInfo->seatclass,true);

            }

            $urlJourneyDate = $journeyDate;
            $inventory = $this->collectApiSeatInventory(
                (int) $subTripId,
                $urlJourneyDate,
                null,
                (int) $pickContext['pick_stand_id']
            );
            $boardingCalendarDate = $inventory['boarding_date'];
            $journeyDate = $inventory['bus_start_date'];
            $bookedSeatslot = $inventory['booked'];
            $blockseat = $inventory['block'];
            // Mobile apps often style only isHold (yellow); admin holds arrive in block — merge for display.
            $holdSeats = array_values(array_unique(array_merge($inventory['hold'], $blockseat)));

            // print_r($bookedSeatslot);

            // exit;

            $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;



            $this->db->transStart();

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

            $query = $builder->select('id, name')->get();



            $seatclassName = [];

            foreach ($query->getResult() as $row) {

                $seatclassName[$row->id] = $row->name;

            }



            $this->db->transComplete();



            // build seat group/row

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

            $seatRows = array_map(function ($singleRow) use ($seatLayoutArr, $tSeatRowWithB, $bookedSeatslot, $holdSeats, $blockseat, $seatData, $seatclassName) {

            // print_r($tSeatRowWithB);die;

                $newSingleRow = array();

                $currentTotalIndex = 0;

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

                    $seatRowIndex++;

                        $seatClass = 0;

                        if(isset($seatData) && !empty($seatData)){

                            foreach ($seatData as $group) {

                                if (in_array($singleSeat, $group['seatNo'])) {

                                    $seatClass = $group['seatClass'];

                                    break;

                                }

                            }    

                        }

                    $sk = trim((string) $singleSeat);
                    $isBooked = in_array($sk, $bookedSeatslot, true) ? 1 : 0;
                    if (!$isBooked) {
                        foreach ($bookedSeatslot as $bs) {
                            if (trim((string) $bs) !== '' && (string) $bs === $sk) {
                                $isBooked = 1;
                                break;
                            }
                        }
                    }
                    $isBlocked = in_array($sk, (array) $blockseat, true) ? 1 : 0;
                    $isHold = (! $isBooked && ($isBlocked || in_array($sk, (array) $holdSeats, true))) ? 1 : 0;

                    $newSingleRow[] = array(

                        'seatName' => $singleSeat, 

                        'isBooked' => $isBooked,

                        'isHold' => $isHold,

                        'isBlocked' => $isBlocked,

                        'seatClass' => $seatClass,

                        'seatClassName' => $seatClass?$seatclassName[$seatClass]:"Economy"

                    );



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

                        $newSingleRow[] = '';

                        $currentTotalIndex += current($seatLayoutArr);

                        array_shift($seatLayoutArr);

                    }

                }



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

            }, $seatRowGroup);

            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]);

            }



            if ($lastSeat !== '') {

                // last seat exists

                // build last seat info

                $skLast = trim((string) $lastSeat);
                $lb = in_array($skLast, $bookedSeatslot, true) ? 1 : 0;
                if (!$lb) {
                    foreach ($bookedSeatslot as $bs) {
                        if (trim((string) $bs) !== '' && (string) $bs === $skLast) {
                            $lb = 1;
                            break;
                        }
                    }
                }
                $lastBlocked = in_array($skLast, (array) $blockseat, true) ? 1 : 0;
                $lh = (! $lb && ($lastBlocked || in_array($skLast, (array) $holdSeats, true))) ? 1 : 0;

                $lastSeatInfo =  array(

                    'seatName' => $lastSeat,

                    'isBooked' => $lb,

                    'isHold' => $lh,

                    'isBlocked' => $lastBlocked,

                );

                

                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;

                }

            }

            $getFleetDetails = $this->fleetTypeModel->where('status', 1)->find($subtripInfo->fleet_id);

            $totalseat = (int) $getFleetDetails->total_seat + (int)$getFleetDetails->last_seat;

            $data = [

                'status' => "success",

                'response' => 200,

                'requires_pickstand' => $pickContext['requires_pickstand'],

                'eligible_pick_stands' => $pickContext['eligible_pick_stands'],

                'layout' =>  $getFleetDetails->layout ?? [],

                'seatlayout' => $seatRows,

                'totalseat' => $fleetInfo->total_seat,

                'totalSeatInRow' => $totalSeatInRow,

                'fleetTotalSeat' => $fleetTotalSeat,

                'seatinfo' => $seatinfo,

            ];

        }else{

            $data = [

                'status' => "Data not found.",

                'response' => 400,

            ];

            

        }



        return $this->response->setJSON($data);

    }

    public function busSeatLayoutNew($subTripId, $journeyDate)

    {
        $pickContext = $this->resolveApiPickstandSeatContext((int) $subTripId, (string) $journeyDate);
        if ($pickContext['awaiting_pickstand']) {
            return $this->buildDeferredPickstandSeatResponse($pickContext);
        }

        $subTripId = $subTripId;

        $subtripInfo = $this->subtripModel

            ->select('subtrips.*, trips.fleet_id')

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

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

            ->first();

        $fleetId = $subtripInfo->fleet_id;

        $inventory = $this->collectApiSeatInventory(
            (int) $subTripId,
            $journeyDate,
            null,
            (int) $pickContext['pick_stand_id']
        );
        $bookedSeats = $inventory['booked'];
        $blockSeats = $inventory['block'];
        $holdSeats = array_values(array_unique(array_merge($inventory['hold'], $blockSeats)));

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

        $fleetTotalSeat = $fleetInfo->total_seat;

        $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++;

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

                $newSingleRow[] = array(

                    'seatName' => $singleSeat, 

                    'isBooked' => $isBooked,

                    'isHold' => $isHold,

                    'isBlocked' => $isBlocked,

                );



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

                    $newSingleRow[] = '';

                    $currentTotalIndex += current($seatLayoutArr);

                    array_shift($seatLayoutArr);

                }

            }



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

        }, $seatRowGroup);



        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);die;

        if ($lastSeat !== '') {

            // last seat exists

            // build last seat info

            $lastSeatInfo =  array(

                'seatName' => $lastSeat,

                'isBooked' => in_array($lastSeat, $bookedSeats) 

            );

            

            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'){

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

                $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'));

        }

    }

    public function processPayment()

    {

        // $this->writeLogsInFile("geidea",$this->request->getVar());

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

        $userId             = $this->request->getVar('user_id');

        $tripId             = $this->request->getVar('trip_id');

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

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

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

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

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

        $totalprice         = $this->request->getVar('amount');

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

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

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

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

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

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

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

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

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



        // Logged-in users may already provide user_id; guests rely on email/mobile lookup/creation.
        $passengerResolution = $this->resolvePassengerForApi($login_email, $login_mobile, $userId);
        if (isset($passengerResolution['error'])) {
            return $this->response->setJSON($passengerResolution['error']);
        }
        $resolvedUserId = $passengerResolution['user_id'];

        if (empty($resolvedUserId)) {
            return $this->response->setJSON([
                'message' => 'Guest user details are missing or invalid. Please provide valid email/mobile before payment.',
                'status' => 'fail',
                'response' => 422,
                'data' => null,
            ]);
        }

        $overnightRun = new \App\Libraries\OvernightTripRunSupport();
        $pickstandMessage = $overnightRun->validatePickStandForSeatInventory(
            (int) $tripId,
            (int) $subtripId,
            (int) $pickstand
        );
        if ($pickstandMessage !== null) {
            return $this->response->setJSON([
                'message' => $pickstandMessage,
                'status' => 'fail',
                'response' => 422,
                'data' => null,
            ]);
        }

        // $this->writeLogsInFile("geidea",var_dump($amount));

        // $this->writeLogsInFile("geidea",var_dump($amount));

        // $this->writeLogsInFile("geidea",$this->request->getVar());

        
        $requestedJourneyDate = BookingInventorySupport::normalizeCalendarDate((string) $journeyDate);
        $bookingDates = BookingInventorySupport::resolveBookingStorageDates(
            (int) $tripId,
            (int) $subtripId,
            $requestedJourneyDate,
            (int) $pickstand,
            null,
            $this->resolveBoardingDateHintFromRequest()
        );
        $busStartDate = $bookingDates['bus_start'];
        $journeyDate  = $bookingDates['boarding'];
        $requestedSeats = array_map('trim', array_filter(explode(',', (string) $seatnumbers)));
        $ticketToken = $this->resolveSeatHoldTokenForPayment(
            (int) $subtripId,
            $requestedSeats,
            (int) $tripId,
            $busStartDate,
            $requestedJourneyDate,
            (int) $pickstand
        );
        $this->cleanExpiredTemporaryBooks($ticketToken);
        $unavailable = $this->isAlreadybookedsubtrip(
            (int) $pick_location_id,
            (int) $drop_location_id,
            (int) $subtripId,
            $busStartDate,
            (int) $tripId,
            $ticketToken,
            $requestedJourneyDate,
            (int) $pickstand
        );
        $seatConflict = array_intersect($requestedSeats, $unavailable);
        if (! empty($seatConflict)) {
            $seatBilingual = admin_bilingual_lang_strings('Localize.seat_no_longer_available_journey', ['seats' => implode(',', $seatConflict)]);
            return $this->response->setJSON([
                'message' => $seatBilingual['en'],
                'message_ar' => $seatBilingual['ar'],
                'status' => 'fail',
                'response' => 409,
                'data' => $this->buildSeatConflictDebugPayload(
                    $seatConflict,
                    $ticketToken,
                    (int) $subtripId,
                    (int) $tripId,
                    $busStartDate,
                    $requestedJourneyDate,
                    (int) $pickstand,
                    $unavailable
                ),
            ]);
        }

        $clientDiscount = ($discount !== null && $discount !== '' && is_numeric($discount))
            ? (float) $discount
            : null;
        $fareCheck = $this->runOneWayOfferFareCheck(
            (int) $tripId,
            (int) $subtripId,
            $requestedSeats,
            (string) ($offerer ?? ''),
            (float) $amount,
            $clientDiscount,
            (float) ($tax ?? 0)
        );
        if (isset($fareCheck['error'])) {
            return $this->response->setJSON($fareCheck['error']);
        }
        if (! empty($fareCheck['expected'])) {
            $totalprice = $fareCheck['expected']['list_price'];
            $discount = $fareCheck['expected']['offer_discount'];
            $grandtotal = $fareCheck['expected']['grand_total'];
            $amount = $fareCheck['expected']['grand_total'];
        }

        if (!$amount || !is_numeric($amount)) {

            $data = [

                'message' => 'Invalid or missing amount.',

                'status' => 'fail',

                'response' => 201,

                'data' => $amount,

            ];

            return $this->response->setJSON($data);

        }



        $geideaData = $this->geideaWaveModel->first();



        if (!$geideaData) {

            $data = [

                'message' => 'Geidea credentials not found.',

                'status' => 'fail',

                'response' => 201,

                'data' => null,

            ];

            return $this->response->setJSON($data);

        }



        if ($geideaData->environment == 0) {

            $publicKey = $geideaData->test_public_key;

            $apiPassword = $geideaData->test_api_pass;

        } else {

            $publicKey = $geideaData->live_public_key;

            $apiPassword = $geideaData->live_api_pass;

        }



        $url = 'https://api.merchant.geidea.net/payment-intent/api/v2/direct/session';

        $merchantReferenceId = "order" . time();

        $timestamp = gmdate("Y-m-d\TH:i:s\Z");

        $signature = $this->generateGeideaSignature($publicKey, $amount, 'EGP', $merchantReferenceId, $apiPassword, $timestamp);



        $payload = [

            'amount' => (float) $amount,

            'currency' => 'EGP',

            'language' => $this->resolveGeideaSessionLanguage(),

            'merchantReferenceId' => $merchantReferenceId,

            'timestamp' => $timestamp,

            'signature' => $signature,

            'callbackUrl' => base_url('modules/api/v1/tickets/payment-sucess'),

        ];



        try {

            $client = \Config\Services::curlrequest();

            $response = $client->post($url, [

                'auth' => [$publicKey, $apiPassword],

                'json' => $payload,

            ]);



            $body = json_decode($response->getBody(), true);

            $sessionId = $body['session']['id'];

            $data = [

                        'user_id'           => $resolvedUserId,

                        'trip_id'           => $tripId,

                        'subtrip_id'        => $subtripId,

                        'merchant_id'       => $merchantReferenceId,

                        'sessionId'         => $sessionId,

                        'amount'            => $amount,

                        "pick_location_id"  => $pick_location_id,

                        "drop_location_id"  => $drop_location_id,

                        "pickstand"         => $pickstand,

                        "dropstand"         => $dropstand,

                        "totalprice"        => $totalprice,

                        "discount"          => $discount,

                        "tax"               => $tax,

                        "grandtotal"        => $grandtotal,

                        "refund"            => 0,

                        "offerer"           => $offerer,

                        "seatnumbers"       => $seatnumbers,

                        "totalseat"         => $totalseat,

                        // Store search date (Flutter journeyDate), not resolved boarding — bookticketGeidea resolves again.
                        'journeydata'       => $requestedJourneyDate,

                    ];

            $this->db->transStart();

            $this->db->table('paystatusgeidea')->insert($data);

            $this->db->transComplete();

            



            if ($response->getStatusCode() === 200 && isset($body['session']['id'])) {

                $data = [

                    'message' => 'Session created successfully.',

                    'status' => 'success',

                    'response' => 200,

                    'data' => [

                        'sessionId' => $body['session']['id']

                        // 'csrfTokenName' => csrf_token(),

                        // 'csrfToken' => csrf_hash()

                    ]

                ];

            } else {

                $data = [

                    'message' => 'Session ID missing in response.',

                    'status' => 'fail',

                    'response' => 201,

                    'data' => $body

                ];

            }

        } catch (\Exception $e) {

            $data = [

                'message' => 'Payment session creation failed: ' . $e->getMessage(),

                'status' => 'fail',

                'response' => 500,

                'data' => null

            ];

        }



        return $this->response->setJSON($data);

    }

    public function paymentSucess(){

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

        $this->writeLogsInFile("geidea",$callbackData);

        $sessionId          = $callbackData->sessionId;

        $merchantId         = $callbackData->order->merchantReferenceId;

        $transactionStatus  = $callbackData->order->transactions[0]->status;

        $orderId            = $callbackData->order->orderId;

        $this->writeLogsInFile("geidea",$orderId);

        $this->writeLogsInFile("geidea",$merchantId);

        $updateData = [

                'status'    => $transactionStatus,

                'order_id'  =>  $orderId

            ];

        $this->db->transStart();

        $this->db->table('paystatusgeidea')

            ->where('sessionId', $sessionId)

            ->where('merchant_id', $merchantId)

            ->update($updateData);

        $this->db->transComplete();



        if($transactionStatus == "Success"){

            $this->writeLogsInFile('geideaBook', $sessionId);

            $this->writeLogsInFile('geideaBook', $merchantId);

            $this->bookticketGeidea($sessionId, $merchantId);

        }



    }

    private function generateGeideaSignature($merchantPublicKey, $orderAmount, $orderCurrency, $orderMerchantReferenceId, $apiPassword, $timestamp) {

        // Ensure the amount is properly formatted

        $amountStr = number_format($orderAmount, 2, '.', '');



        // Build the string to hash

        $data = "{$merchantPublicKey}{$amountStr}{$orderCurrency}{$orderMerchantReferenceId}{$timestamp}";



        // Generate the HMAC SHA256 hash

        $hash = hash_hmac('sha256', $data, $apiPassword, true);



        // Return the signature as a base64 encoded string

        return base64_encode($hash);

    }

    private function writeLogsInFile($logFileName, $msgArray)

    {

        $date = egypt_today();

        $logDir = WRITEPATH . 'logs/geidea'; // Safe writable path

        if (!is_dir($logDir)) {

            mkdir($logDir, 0777, true); // create if not exists

        }



        $filePath = $logDir . '/' . $logFileName . '_' . $date . '.log';



        $logEntry = '[' . egypt_now_sql() . '] ' . print_r($msgArray, true) . "\n";



        file_put_contents($filePath, $logEntry, FILE_APPEND);

    }

    public function bookticketGeidea($sessionId, $merchantId){

        // $sessionId = $this->request->getVar('sessionId');

        // $merchantId = $this->request->getVar('merchantId');

        $this->writeLogsInFile('geideaBook', $sessionId);

        $this->writeLogsInFile('geideaBook', $merchantId);

        $this->db->transStart();

        $results = $this->db->table('paystatusgeidea')

            ->select('users.*, paystatusgeidea.*, paystatusgeidea.id as paystatusgeidea_id')

            ->join('users', 'users.id = paystatusgeidea.user_id' ,"left")

            ->where('sessionId', $sessionId)

            ->where('merchant_id', $merchantId)

            ->get()

            ->getResult();

        $this->writeLogsInFile('geideaBook', $results);

        $this->db->transComplete();

        foreach ($results as $result) {

            $userId         = $result->user_id;

            $tripId         = $result->trip_id;

            $subtripId      = $result->subtrip_id;

            $journeyDate    = $result->journeydata;

            $pickupLocation = $result->pick_location_id;

            $dropLocation = $result->drop_location_id;

            $pickstand = $result->pickstand;

            $dropstand = $result->dropstand;

            $searchCalendar = BookingInventorySupport::normalizeCalendarDate((string) $journeyDate);
            $geideaBookingDates = BookingInventorySupport::resolveBookingStorageDates(
                (int) $tripId,
                (int) $subtripId,
                $searchCalendar,
                (int) $pickstand,
                null,
                null
            );
            $journeyDate          = $geideaBookingDates['boarding'];
            $busStartDate         = $geideaBookingDates['bus_start'];
            // Match processPayment / hold: search date from paystatusgeidea (not boarding).
            $requestedJourneyDate = $searchCalendar;

            // paystatusgeidea: totalprice = leg list/base fare; amount/grandtotal = actually charged for that leg.
            $chargedForLeg = (float) $result->amount;
            $legListPrice = isset($result->totalprice) ? (float) $result->totalprice : 0.0;
            if ($legListPrice <= 0) {
                $legListPrice = $chargedForLeg;
            }
            $totalprice = $legListPrice;

            $discount = $result->discount;
            $roundtripdiscount = (float) ($result->roundtrip_discount ?? 0);
            
            $tax = $result->tax;

            $grandtotal = isset($result->grandtotal) && $result->grandtotal !== '' && is_numeric($result->grandtotal)
                ? (float) $result->grandtotal
                : $chargedForLeg;

            $offerer = $result->offerer;

            $seatnumbers = trim((string) ($result->seatnumbers ?? ''));

            $totalseat = (int) ($result->totalseat ?? 0);
            if ($totalseat <= 0 && $seatnumbers !== '') {
                $totalseat = count(array_filter(array_map('trim', explode(',', $seatnumbers))));
            }
            
            $ticketmailLibrary = new Ticketmail();

            $ticketid = null;

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

            $rand = "TB" . $rand;





            $login_email = $result->login_email;

            $login_mobile = $result->login_mobile;

            $this->db->transStart();

            $passengerResolution = $this->resolvePassengerForApi($login_email, $login_mobile);
            if (isset($passengerResolution['error'])) {
                $this->writeLogsInFile('geideaBook', $passengerResolution['error']);
                return false;
            }
            $userid = $passengerResolution['user_id'];

            $seatList = array_values(array_filter(array_map('trim', explode(',', (string) $seatnumbers))));
            $clientDiscount = ($discount !== null && $discount !== '' && is_numeric($discount))
                ? (float) $discount
                : null;
            if ((float) $roundtripdiscount <= 0) {
                $fareCheck = $this->runOneWayOfferFareCheck(
                    (int) $tripId,
                    (int) $subtripId,
                    $seatList,
                    (string) ($offerer ?? ''),
                    (float) $grandtotal,
                    $clientDiscount,
                    (float) ($tax ?? 0)
                );
                if (isset($fareCheck['error'])) {
                    $this->writeLogsInFile('geideaBook', $fareCheck['error']);
                    return false;
                }
                if (! empty($fareCheck['expected'])) {
                    $totalprice = $fareCheck['expected']['list_price'];
                    $discount = $fareCheck['expected']['offer_discount'];
                    $grandtotal = $fareCheck['expected']['grand_total'];
                }
            } else {
                try {
                    $serverLeg = BookingOfferFareService::calculateLegFare(
                        (int) $tripId,
                        (int) $subtripId,
                        $seatList,
                        (string) ($offerer ?? ''),
                        0.0,
                        (float) $roundtripdiscount
                    );
                    $totalprice = $serverLeg['list_price'];
                    $discount = $serverLeg['offer_discount'];
                } catch (\InvalidArgumentException $e) {
                    $this->writeLogsInFile('geideaBook', $e->getMessage());
                    return false;
                }
            }

            $ticketbooking = array(

                "booking_id" => $rand,

                "trip_id" => $tripId,

                "subtrip_id" => $subtripId,

                "passanger_id" => $userid,

                "pick_location_id" => $pickupLocation,

                "drop_location_id" => $dropLocation,

                "pick_stand_id" => $pickstand,

                "drop_stand_id" => $dropstand,

                "price" => $totalprice,

                "discount" => $discount,

                "roundtrip_discount" => $roundtripdiscount,

                "totaltax" => $tax,

                "paidamount" => $grandtotal,

                "refund" => 0,

                "bookby_user_id" => $userid,

                "bookby_user_type" => "passanger",

                "journeydata" => $journeyDate,

                "pay_method_id" => 6,

                "payment_status" => "paid",

                "payment_detail" => "",

                "vehicle_id" => 0,

                "cancel_status" => 0,



                "offerer" => $offerer,



                "seatnumber" => $seatnumbers,

                "totalseat" => $totalseat,



            );



            $validTicketbooking = array(



                "booking_id" => $rand,

                "trip_id" => $tripId,

                "subtrip_id" => $subtripId,

                "passanger_id" => $userid,



                "pick_location_id" => $pickupLocation,

                "drop_location_id" => $dropLocation,

                "pick_stand_id" => $pickstand,

                "drop_stand_id" => $dropstand,



                "price" => $totalprice,

                "paidamount" => $grandtotal,

                "seatnumber" => $seatnumbers,

                "totalseat" => $totalseat,

                "bookby_user_id" => 1,

                "journeydata" => $journeyDate,



                "payment_status" => "paid",

                "vehicle_id" => 0,



            );





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

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

                $ticketbooking['journeydata']      = $journeyDate;
                $validTicketbooking['journeydata'] = $journeyDate;

                $requestedSeats = array_map('trim', array_filter(explode(',', $seatnumbers)));
                $holdToken = $this->resolveSeatHoldTokenForPayment(
                    (int) $subtripId,
                    $requestedSeats,
                    (int) $tripId,
                    $busStartDate,
                    $requestedJourneyDate,
                    (int) $pickstand
                );
                $allBookedSeats = $this->isAlreadybookedsubtrip(
                    (int) $pickupLocation,
                    (int) $dropLocation,
                    (int) $subtripId,
                    $busStartDate,
                    (int) $tripId,
                    $holdToken,
                    $requestedJourneyDate,
                    (int) $pickstand
                );
                $intersection = array_intersect($requestedSeats, $allBookedSeats);
                if (! empty($intersection)) {
                    $this->db->transRollback();

                    $seatBilingual = admin_bilingual_lang_strings('Localize.seat_already_booked_segment', ['seats' => implode(',', $intersection)]);
                    return $this->response->setJSON([
                        'message' => $seatBilingual['en'],
                        'message_ar' => $seatBilingual['ar'],
                        'status' => 'fail',
                        'response' => 409,
                        'data' => null,
                    ]);
                }

                $paymentStatus = "paid";

                $paidamount = $grandtotal;

                $ticketid = $this->ticketModel->insert($ticketbooking);

                if (empty($ticketid)) {

                    $data = [

                        'message' => "booking data error",

                        'status' => "success",

                        'response' => 204,

                        'data' => "booking data not appropriate",

                    ];

                    return $this->response->setJSON($data);

                }

                

                $partialPaid = array(

                    "booking_id" => $rand,

                    "trip_id" => $tripId,

                    "subtrip_id" => $subtripId,

                    "passanger_id" => $userid,

                    "paidamount" => $paidamount,



                );

                $paidpartial = array(

                    "booking_id" => $rand,

                    "trip_id" => $tripId,

                    "subtrip_id" => $subtripId,

                    "passanger_id" => $userid,

                    "paidamount" => $paidamount,

                    "pay_method_id" => 6,

                    "payment_detail" => "paid",

                );

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



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



                    $maitripid = $tripId;

                    $subtripid = $subtripId;

                    $piclocation = $pickupLocation;

                    $droplocation = $dropLocation;

                    $pick_stand_id = $pickstand;

                    $drop_stand_id = $dropstand;

                    $requestedSeats = array_values(array_filter(array_map('trim', explode(',', (string) $seatnumbers))));
                    $seatCount      = max((int) $totalseat, count($requestedSeats));
                    if ($seatCount <= 0) {
                        $seatCount = 1;
                    }

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


                    $type = "income";

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

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



                    $this->writeLogsInFile("BookJourney", $journeylist);



                    if (empty($journeylist)) {

                        $data = [

                            'status' => "fail",

                            'response' => 204,

                            'data' => "journey list data not inserted",

                        ];

                    }



                    $this->db->transComplete();

                    

                    $updateData = ['ticket_id' => $ticketid];

                    $this->db->transStart();

                    $this->db->table('paystatusgeidea')

                            ->where('id', $result->paystatusgeidea_id)

                            ->where('merchant_id', $merchantId)

                            ->update($updateData);

                    $this->db->transComplete();



                    $ticketInfo =  $this->ticketModel->find($ticketid);



                    $emaildata = $ticketmailLibrary->getticketEmailData($rand);

                    try {
                        if (! empty($emaildata['ticket'])) {
                            $status = sendTicket($login_email, $emaildata);
                        } else {
                            $status = false;
                        }
                    } catch (\Throwable $e) {
                        log_message('error', 'API Geidea ticket email failed for {booking}: {message}', [
                            'booking' => $rand,
                            'message' => $e->getMessage(),
                        ]);
                        $status = false;
                    }



                    if ($status == true) {

                        $data = [

                            'status' => "success",

                            'response' => 200,

                            'data' => $ticketInfo,

                        ];

                    } else {

                        $data = [

                            'status' => "success",

                            'response' => 200,

                            'data' => $ticketInfo,

                            'emailerror' => $status,

                        ];

                    }

                } else {



                    $errors = $this->validation;

                    $data = [

                        'message' => "Booking & Paid Information Not Valid",

                        'status' => "failed",

                        'response' => 204,

                        'errors' => $errors->listErrors(),

                    ];

                }





            } else {



                $errors = $this->validation;

                $data = [

                    'message' => "Booking Information Not Valid",

                    'status' => "failed",

                    'response' => 204,

                    'errors' => $errors,

                ];

            }

        }

        // $emaildata = $ticketmailLibrary->getticketEmailData($rand);



        // $status = sendTicket($login_email, $emaildata);





    }

    private function bookjourneyGeidea(
        $rand,
        $userid,
        $maitripid,
        $subtripid,
        $piclocation,
        $droplocation,
        $pick_stand_id,
        $drop_stand_id,
        $manifestDate,
        $boardingDate = null,
        int $seatCount = 1
    ) {
        $manifestDate = BookingInventorySupport::normalizeCalendarDate((string) $manifestDate);
        $boardingDate = $boardingDate !== null
            ? BookingInventorySupport::normalizeCalendarDate((string) $boardingDate)
            : $manifestDate;

        BookingInventorySupport::logBookingDateContext('bookjourneyGeidea', [
            'trip_id' => (int) $maitripid,
            'subtrip_id' => (int) $subtripid,
            'search_date' => $manifestDate,
            'pickstand' => (int) $pick_stand_id,
            'service_date' => $manifestDate,
            'boarding_date' => $boardingDate,
            'stored_ticket_date' => $boardingDate,
            'stored_manifest_date' => $manifestDate,
            'booking_id' => $rand,
        ]);

        $joruneylistid = null;



        $passengerdetail = $this->userDetailModel->where('user_id', $userid)->first();

        $firstname = $passengerdetail->first_name??"";

        $lastname = $passengerdetail->last_name?? "";

        $passengerdetails = $this->userModel->find($userid);

        $mobile = $passengerdetails->login_mobile ?? "";

        $email = $passengerdetails->login_email ?? "";

        $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" => $firstname,

            "last_name" => $lastname,

            "phone" => $mobile,

            "journeydate" => $manifestDate

        );



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



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

        }

        $seatCount = max(1, $seatCount);
        if ($seatCount > 1) {
            $placeholderRows = [];
            for ($i = 1; $i < $seatCount; $i++) {
                $placeholderRows[] = [
                    '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'       => '',
                    'last_name'        => '',
                    'phone'            => '',
                    'journeydate'      => $manifestDate,
                    'id_number'        => '',
                ];
            }
            if ($placeholderRows !== []) {
                $this->journeylistModel->insertBatch($placeholderRows);
            }
        }

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

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

        $newPassangerMobile = $this->request->getVar('login_mobile_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" => $manifestDate,

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



                );

            }

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

        }

        return $joruneylistid;



    }



    public function getDynamicPickupDropCombinations($pickup, $drop, $tripSeries, $date, $tripId, $requestingSubtripId = null, $userJourneyDate = null, $pickStandId = 0)

    {

        $stops = array_values(array_filter(array_map('trim', explode(',', (string) $tripSeries))));
        if (empty($stops)) {
            return [];
        }

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

        $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;

    }

    /**
     * @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];
            }
        }

        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, $userJourneyDate = null, $pickStandId = 0)

    {

        $stops = array_values(array_filter(array_map('trim', explode(',', (string) $tripSeries))));
        if (empty($stops)) {
            return [];
        }

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

        $reqSub = $subtripId ? $this->db->table('subtrips')->where('id', (int) $subtripId)->get()->getRow() : null;
        $spanPick = $pickup;
        $spanDrop = $drop;
        $tripFilter = 0;

        if ($reqSub) {
            $tripFilter = (int) $reqSub->trip_id;
        } else {
            $tripFilter = (int) $subtripId;
        }

        if (!$this->canResolveSegment($stops, $spanPick, $spanDrop)) {
            return [];
        }

        $requestStoppage = $reqSub ? trim((string) ($reqSub->stoppage ?? '')) : '';

        $departureDates = BookingInventorySupport::resolveSearchDepartureDates(
            $tripFilter,
            (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);
        TemporaryBookHoldService::applyActiveHoldExpiryFilter($builder);
        $builder->where('trips.status', 1)
            ->where('subtrips.trip_id', $tripFilter);

        if ($ignoreToken) {
            $builder->where('TRIM(temporarybooks.ticket_token) !=', trim($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;
    }

    /**
     * @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 listCompany(){

    //     $companyData = $this->companyModel->findAll();

    //     $data = [

    //                 'status' => "success",

    //                 'response' => 200,

    //                 'companyName' =>  $companyData

    //             ];

    //     return $this->response->setJSON($data);

    // }

    public function listCompany()

    {

        $id = $this->request->getGet('id');

        $name = $this->request->getGet('name');



        $builder = $this->companyModel;



        if ($id) {

            $builder = $builder->where('id', $id);

        }



        if ($name) {

            $builder = $builder->like('name', $name); // for partial match

        }



        $companyData = $builder->findAll();



        $data = [

            'status' => 'success',

            'response' => 200,

            'companyList' => $companyData

        ];



        return $this->response->setJSON($data);

    }

    public function checkPaymentStatus($orderId)

    {

        // Replace with your actual Geidea credentials

        // $publicKey = 'your_public_key';

        // $accessKey = 'your_access_key';

        

        $geideaData = $this->geideaWaveModel->first();



        if (!$geideaData) {

            $data = [

                'message' => 'Geidea credentials not found.',

                'status' => 'fail',

                'response' => 201,

                'data' => null,

            ];

            return $this->response->setJSON($data);

        }



        if ($geideaData->environment == 0) {

            $publicKey = $geideaData->test_public_key;

            $apiPassword = $geideaData->test_api_pass;

        } else {

            $publicKey = $geideaData->live_public_key;

            $apiPassword = $geideaData->live_api_pass;

        }









        // Geidea API URL (choose live or sandbox)

        $url = "https://api.geidea.net/v1/direct/orders/" . $orderId;

        // For sandbox: use https://api.merchant.geidea.net/v1/direct/orders/...



        $client = \Config\Services::curlrequest();



        try {

            $response = $client->request('GET', $url, [

                'auth' => [$publicKey, $accessKey],

                'headers' => [

                    'Accept' => 'application/json',

                    'Content-Type' => 'application/json',

                ]

            ]);



            $body = json_decode($response->getBody(), true);



            if (isset($body['order'])) {

                $status = $body['order']['status'] ?? null;

                $detailedStatus = $body['order']['detailedStatus'] ?? null;



                if ($status === 'Success' && $detailedStatus === 'Paid') {

                    return $this->respond([

                        'status' => 'success',

                        'message' => 'Payment completed',

                        'data' => $body['order']

                    ]);

                } else {

                    return $this->respond([

                        'status' => 'pending_or_failed',

                        'message' => 'Payment not completed',

                        'order_status' => $detailedStatus,

                        'data' => $body['order']

                    ]);

                }

            } else {

                return $this->fail('Invalid response from Geidea');

            }

        } catch (\Exception $e) {

            return $this->failServerError('Error checking payment status: ' . $e->getMessage());

        }

    }

    public function paymentSucessStatic(){

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

        $sessionId = $this->request->getVar('session_id');

        $data = [];

        if($status == "sucess"){

            $result = $this->db->table('paystatusgeidea')

                        ->select('users.*, paystatusgeidea.*, paystatusgeidea.id as paystatusgeidea_id')

                        ->join('users', 'users.id = paystatusgeidea.user_id' ,"left")

                        ->where('sessionId', $sessionId)

                        ->get()

                        ->getRow();

            if($this->bookticketGeidea($sessionId, $result->merchant_id)){

                $data = [

                    'message' => 'Mail set successfully.',

                    'status' => 'success',

                    'response' => 200,

                    'data' => "Ticket booked Successfully",

                ];

            }

        }

        return $this->response->setJSON($data);

    }

    function getSeatClasses($seatsData, $seatNumbers) {

        if (is_string($seatsData)) {

            $seatsData = json_decode($seatsData, true);

        }



        if (!is_array($seatNumbers)) {

            $seatNumbers = explode(",", $seatNumbers);

        }



        $seatClasses = [];

        $seatClassesArray = [];

        // print_r($seatNumbers);exit;

        foreach ($seatNumbers as $seatNo) {

            // $found = false; 

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

                foreach ($seatsData as $seatInfo) {

                    if (in_array($seatNo, $seatInfo['seatNo'])) {

                        $seatClasses[$seatNo] = $seatInfo['seatClass'];

                        // $found = true;

                        break;

                    }

                }

            }

            // if (!$found) {

            //     $seatClasses[$seatNo] = "adult seat";

            // }

            // $seatClassesArray[] = $seatClasses[$seatNo];

        }

        // $uniqueSeatClassesArray = array_unique($seatClassesArray);

        return $seatClasses;



    }

    public function roundtripprocessPayment()

    {

        // $this->writeLogsInFile("geidea",$this->request->getVar());

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

        $userId             = $this->request->getVar('user_id');

        $tripId             = $this->request->getVar('trip_id');

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

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

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

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

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

        $totalprice         = $this->request->getVar('amount');

        $discount           = $this->request->getVar('discount');
        
        $roundtripdiscount  = $this->request->getVar('roundTripDiscount') ?? 0;

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

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

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

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

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

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

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

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

        $fare               = $this->request->getVar('fare')??"";



        $returntripId             = $this->request->getVar('return_trip_id');

        $returnsubtripId          = $this->request->getVar('return_subtrip_id');

        $returnpick_location_id   = $this->request->getVar('return_pick_location_id');

        $returndrop_location_id   = $this->request->getVar('return_drop_location_id');

        $returnpickstand          = $this->request->getVar('return_pickstand');

        $returndropstand          = $this->request->getVar('return_dropstand');

        $returnseatnumbers        = $this->request->getVar('return_seatnumbers');

        $returntotalseat          = $this->request->getVar('return_totalseat');

        $returnjourneyDate        = $this->request->getVar('return_journeyDate');

        $returnfare               = $this->request->getVar('return_fare')??"";

        
        $requestedOutbound = BookingInventorySupport::normalizeCalendarDate((string) $journeyDate);
        $outboundPickStand = (int) $pickstand;
        $overnightRun      = new \App\Libraries\OvernightTripRunSupport();
        $outPickstandMessage = $overnightRun->validatePickStandForSeatInventory(
            (int) $tripId,
            (int) $subtripId,
            $outboundPickStand
        );
        if ($outPickstandMessage !== null) {
            return $this->response->setJSON([
                'message' => $outPickstandMessage,
                'status' => 'fail',
                'response' => 422,
                'data' => null,
            ]);
        }
        $returnPickStand = (int) $returnpickstand;
        $retPickstandMessage = $overnightRun->validatePickStandForSeatInventory(
            (int) $returntripId,
            (int) $returnsubtripId,
            $returnPickStand
        );
        if ($retPickstandMessage !== null) {
            return $this->response->setJSON([
                'message' => $retPickstandMessage,
                'status' => 'fail',
                'response' => 422,
                'data' => null,
            ]);
        }
        $outboundBookingDates = BookingInventorySupport::resolveBookingStorageDates(
            (int) $tripId,
            (int) $subtripId,
            $requestedOutbound,
            $outboundPickStand,
            null,
            $this->resolveBoardingDateHintFromRequest()
        );
        $outboundBusStart = $outboundBookingDates['bus_start'];
        $outSeats = array_map('trim', array_filter(explode(',', (string) $seatnumbers)));
        $ticketToken = $this->resolveSeatHoldTokenForPayment(
            (int) $subtripId,
            $outSeats,
            (int) $tripId,
            $outboundBusStart,
            $requestedOutbound,
            $outboundPickStand
        );
        $this->cleanExpiredTemporaryBooks($ticketToken);
        $outUnavailable = $this->isAlreadybookedsubtrip(
            (int) $pick_location_id,
            (int) $drop_location_id,
            (int) $subtripId,
            $outboundBusStart,
            (int) $tripId,
            $ticketToken,
            $requestedOutbound,
            $outboundPickStand
        );
        $outConflict = array_intersect($outSeats, $outUnavailable);
        if (! empty($outConflict)) {
            $seatBilingual = admin_bilingual_lang_strings('Localize.seat_no_longer_available_outbound', ['seats' => implode(',', $outConflict)]);
            return $this->response->setJSON([
                'message' => $seatBilingual['en'],
                'message_ar' => $seatBilingual['ar'],
                'status' => 'fail',
                'response' => 409,
                'data' => null,
            ]);
        }
        $requestedReturn = BookingInventorySupport::normalizeCalendarDate((string) $returnjourneyDate);
        $returnReturnBoardingHint = $this->request->getVar('return_boarding_date') ?? $this->request->getVar('return_boardingDate');
        $returnBoardingHint = is_string($returnReturnBoardingHint) && trim($returnReturnBoardingHint) !== ''
            ? BookingInventorySupport::normalizeCalendarDate($returnReturnBoardingHint)
            : null;
        $returnBookingDates = BookingInventorySupport::resolveBookingStorageDates(
            (int) $returntripId,
            (int) $returnsubtripId,
            $requestedReturn,
            $returnPickStand,
            null,
            $returnBoardingHint
        );
        $returnBusStart = $returnBookingDates['bus_start'];
        $retUnavailable = $this->isAlreadybookedsubtrip(
            (int) $returnpick_location_id,
            (int) $returndrop_location_id,
            (int) $returnsubtripId,
            $returnBusStart,
            (int) $returntripId,
            $ticketToken,
            $requestedReturn,
            $returnPickStand
        );
        $retSeats = array_map('trim', array_filter(explode(',', (string) $returnseatnumbers)));
        $retConflict = array_intersect($retSeats, $retUnavailable);
        if (! empty($retConflict)) {
            $seatBilingual = admin_bilingual_lang_strings('Localize.seat_no_longer_available_return', ['seats' => implode(',', $retConflict)]);
            return $this->response->setJSON([
                'message' => $seatBilingual['en'],
                'message_ar' => $seatBilingual['ar'],
                'status' => 'fail',
                'response' => 409,
                'data' => null,
            ]);
        }

        $clientDiscount = ($discount !== null && $discount !== '' && is_numeric($discount))
            ? (float) $discount
            : null;
        $roundTripFareCheck = $this->runRoundTripOfferFareCheck(
            (int) $tripId,
            (int) $subtripId,
            $outSeats,
            (float) $fare,
            (int) $returntripId,
            (int) $returnsubtripId,
            $retSeats,
            (float) $returnfare,
            (string) ($offerer ?? ''),
            (float) $amount,
            $clientDiscount,
            (float) ($tax ?? 0),
            max(0, (float) $roundtripdiscount)
        );
        if (isset($roundTripFareCheck['error'])) {
            return $this->response->setJSON($roundTripFareCheck['error']);
        }
        if (! empty($roundTripFareCheck['expected'])) {
            $exp = $roundTripFareCheck['expected'];
            $amount = $exp['grand_total'];
            $grandtotal = $exp['grand_total'];
            $totalprice = $exp['grand_total'];
            $discount = $exp['offer_discount'];
            $fare = $exp['outbound']['list_price'];
            $returnfare = $exp['return']['list_price'];
        }

        // paystatusgeidea must store search dates; bookticketGeidea resolves boarding once.
        $outboundTicketDate = $requestedOutbound;
        $returnTicketDate   = $requestedReturn;

// Split charged total across legs proportionally to original fares.
        $chargedTotal = round((float) $amount, 2);
        $baseOutboundFare = max(0, (float) $fare);
        $baseReturnFare = max(0, (float) $returnfare);
        $totalBaseFare = $baseOutboundFare + $baseReturnFare;
        if ($totalBaseFare > 0 && $chargedTotal >= 0) {
            $outboundAmount = round(($chargedTotal * $baseOutboundFare) / $totalBaseFare, 2);
            $returnAmount = round($chargedTotal - $outboundAmount, 2);
        } else {
            $outboundAmount = $chargedTotal;
            $returnAmount = 0.00;
        }

        // Split discount value across both legs using same ratio.
        $totalDiscount = max(0, (float) $discount);
        if ($totalBaseFare > 0 && $totalDiscount > 0) {
            $outboundDiscount = round(($totalDiscount * $baseOutboundFare) / $totalBaseFare, 2);
            $returnDiscount = round($totalDiscount - $outboundDiscount, 2);
        } else {
            $outboundDiscount = 0.00;
            $returnDiscount = 0.00;
        }

        // Roundtrip discount is provided as percent from app/web (e.g. 45).
        $roundTripDiscountRaw = max(0, (float) $roundtripdiscount);
        if ($roundTripDiscountRaw > 0 && $totalBaseFare > 0 && $roundTripDiscountRaw <= 100) {
            $roundTripDiscountTotal = round(($totalBaseFare * $roundTripDiscountRaw) / 100, 2);
        } else {
            $roundTripDiscountTotal = $roundTripDiscountRaw;
        }
        if ($totalBaseFare > 0 && $roundTripDiscountTotal > 0) {
            $outboundRoundTripDiscount = round(($roundTripDiscountTotal * $baseOutboundFare) / $totalBaseFare, 2);
            $returnRoundTripDiscount = round($roundTripDiscountTotal - $outboundRoundTripDiscount, 2);
        } else {
            $outboundRoundTripDiscount = 0.00;
            $returnRoundTripDiscount = 0.00;
        }



        // Logged-in users may already provide user_id; guests rely on email/mobile lookup/creation.
        $passengerResolution = $this->resolvePassengerForApi($login_email, $login_mobile, $userId);
        if (isset($passengerResolution['error'])) {
            return $this->response->setJSON($passengerResolution['error']);
        }
        $resolvedUserId = $passengerResolution['user_id'];

        if (empty($resolvedUserId)) {
            return $this->response->setJSON([
                'message' => 'Guest user details are missing or invalid. Please provide valid email/mobile before payment.',
                'status' => 'fail',
                'response' => 422,
                'data' => null,
            ]);
        }

        // $this->writeLogsInFile("geidea",var_dump($amount));

        // $this->writeLogsInFile("geidea",var_dump($amount));

        // $this->writeLogsInFile("geidea",$this->request->getVar());

        if (!$amount || !is_numeric($amount)) {

            $data = [

                'message' => 'Invalid or missing amount.',

                'status' => 'fail',

                'response' => 201,

                'data' => $amount,

            ];

            return $this->response->setJSON($data);

        }



        $geideaData = $this->geideaWaveModel->first();



        if (!$geideaData) {

            $data = [

                'message' => 'Geidea credentials not found.',

                'status' => 'fail',

                'response' => 201,

                'data' => null,

            ];

            return $this->response->setJSON($data);

        }



        if ($geideaData->environment == 0) {

            $publicKey = $geideaData->test_public_key;

            $apiPassword = $geideaData->test_api_pass;

        } else {

            $publicKey = $geideaData->live_public_key;

            $apiPassword = $geideaData->live_api_pass;

        }



        $url = 'https://api.merchant.geidea.net/payment-intent/api/v2/direct/session';

        $merchantReferenceId = "order" . time();

        $timestamp = gmdate("Y-m-d\TH:i:s\Z");

        $signature = $this->generateGeideaSignature($publicKey, $amount, 'EGP', $merchantReferenceId, $apiPassword, $timestamp);



        $payload = [

            'amount' => (float) $amount,

            'currency' => 'EGP',

            'language' => $this->resolveGeideaSessionLanguage(),

            'merchantReferenceId' => $merchantReferenceId,

            'timestamp' => $timestamp,

            'signature' => $signature,

            'callbackUrl' => base_url('modules/api/v1/tickets/payment-sucess'),

        ];



        try {

            $client = \Config\Services::curlrequest();

            $response = $client->post($url, [

                'auth' => [$publicKey, $apiPassword],

                'json' => $payload,

            ]);



            $body = json_decode($response->getBody(), true);

            $sessionId = $body['session']['id'];

            $data = [

                        'user_id'           => $resolvedUserId,

                        'trip_id'           => $tripId,

                        'subtrip_id'        => $subtripId,

                        'merchant_id'       => $merchantReferenceId,

                        'sessionId'         => $sessionId,

                        'amount'            => $outboundAmount,

                        "pick_location_id"  => $pick_location_id,

                        "drop_location_id"  => $drop_location_id,

                        "pickstand"         => $pickstand,

                        "dropstand"         => $dropstand,

                        "totalprice"        => $baseOutboundFare,

                        "discount"          => $outboundDiscount,
                        
                        "roundtrip_discount" => $outboundRoundTripDiscount,

                        "tax"               => $tax,

                        "grandtotal"        => $outboundAmount,

                        "refund"            => 0,

                        "offerer"           => $offerer,

                        "seatnumbers"       => $seatnumbers,

                        "totalseat"         => $totalseat,

                        'journeydata'       => $outboundTicketDate,

                    ];

            $this->db->transStart();

            $this->db->table('paystatusgeidea')->insert($data);

            $this->db->transComplete();



            $returndata = [

                        'user_id'           => $resolvedUserId,

                        'trip_id'           => $returntripId,

                        'subtrip_id'        => $returnsubtripId,

                        'merchant_id'       => $merchantReferenceId,

                        'sessionId'         => $sessionId,

                        'amount'            => $returnAmount,

                        "pick_location_id"  => $returnpick_location_id,

                        "drop_location_id"  => $returndrop_location_id,

                        "pickstand"         => $returnpickstand,

                        "dropstand"         => $returndropstand,

                        "totalprice"        => $baseReturnFare,

                        "discount"          => $returnDiscount,
                        
                        "roundtrip_discount" => $returnRoundTripDiscount,

                        "tax"               => $tax,

                        "grandtotal"        => $returnAmount,

                        "refund"            => 0,

                        "offerer"           => $offerer,

                        "seatnumbers"       => $returnseatnumbers,

                        "totalseat"         => $returntotalseat,

                        'journeydata'       => $returnTicketDate,

                    ];

            $this->db->transStart();

            $this->db->table('paystatusgeidea')->insert($returndata);

            $this->db->transComplete();

            



            if ($response->getStatusCode() === 200 && isset($body['session']['id'])) {

                $data = [

                    'message' => 'Session created successfully.',

                    'status' => 'success',

                    'response' => 200,

                    'data' => [

                        'sessionId' => $body['session']['id']

                        // 'csrfTokenName' => csrf_token(),

                        // 'csrfToken' => csrf_hash()

                    ]

                ];

            } else {

                $data = [

                    'message' => 'Session ID missing in response.',

                    'status' => 'fail',

                    'response' => 201,

                    'data' => $body

                ];

            }

        } catch (\Exception $e) {

            $data = [

                'message' => 'Payment session creation failed: ' . $e->getMessage(),

                'status' => 'fail',

                'response' => 500,

                'data' => null

            ];

        }



        return $this->response->setJSON($data);

    }

    public function getBookingIdFromSession($sessionId)
    {
        try {
            $this->db->transStart();

            // Step 1: Get the ticket_id from paystatusgeidea
            $ticketData = $this->db->table('paystatusgeidea')
                ->select('ticket_id, user_id, created_at')
                ->where('sessionId', $sessionId)
                ->get()
                ->getRow();

            if (!$ticketData) {
                $this->db->transComplete();
                return $this->response->setJSON([
                    'status'  => 'error',
                    'message' => 'No ticket found for the given session ID'
                ]);
            }

            $ticketId = $ticketData->ticket_id;
            $userId = $ticketData->user_id;
            $sessionCreated = $ticketData->created_at;
            

            // Step 2: Get booking_id from tickets table
            $bookingData = $this->db->table('tickets')
                ->select('booking_id')
                ->where('id', $ticketId)
                ->get()
                ->getRow();

            $this->db->transComplete();
            
            $evalue = $this->userModel->where('id',$userId)->first();
            $userCreated = $evalue->created_at;
            
            $userDate = date('Y-m-d', strtotime($userCreated));
            $sessionDate = date('Y-m-d', strtotime($sessionCreated));
            
            if ($userDate === $sessionDate) {
                $is_exist = 0;
            } else {
                $is_exist = 1;
            }


            if (!$bookingData) {
                return $this->response->setJSON([
                    'status'  => 'error',
                    'message' => 'No booking found for the given ticket'
                ]);
            }

            return $this->response->setJSON([
                'status'      => 'success',
                'booking_id'  => $bookingData->booking_id,
                'is_exist'    => $is_exist
            ]);

        } catch (\Exception $e) {
            $this->db->transComplete();
            return $this->response->setJSON([
                'status'  => 'error',
                'message' => $e->getMessage()
            ]);
        }
    }
    
     public function paymentLogs()
    {
        $key = $this->request->getVar('key');
        $status = $this->request->getVar('status'); // 'on' or 'off'
        $message = $this->request->getVar('message') ?: 'System is under maintenance';

        // Verify secret key
        if ($key !== $this->secretKey) {
            return $this->respond([
                'status' => 'error',
                'response' => 401,
                'message' => 'Invalid authorization key'
            ], 401);
        }

        // Validate status
        if (!in_array($status, ['on', 'off'])) {
            return $this->respond([
                'status' => 'error',
                'response' => 400,
                'message' => 'Status must be "on" or "off"'
            ], 400);
        }

        // Update maintenance status
        $sql = "UPDATE system_maintenance SET status = ?, message = ? WHERE id = 1";
        $result = $this->db->query($sql, [$status, $message]);

        if ($result) {
            return $this->respond([
                'status' => 'success',
                'response' => 200,
                'message' => "Maintenance mode set to: {$status}",
                'maintenance_status' => $status,
                'timestamp' => egypt_now_sql()
            ], 200);
        } else {
            return $this->respond([
                'status' => 'error',
                'response' => 500,
                'message' => 'Failed to update maintenance status'
            ], 500);
        }
    }
    
    
    public function generateTicketPDF()
    {
        try {
            $booking_id = $this->request->getVar('booking_id');
            
            if (empty($booking_id)) {
                return $this->response->setJSON([
                    'status' => "fail",
                    'response' => 400,
                    'message' => "Booking ID is required"
                ]);
            }

            // Check if ticket exists
            $ticketExists = $this->ticketModel->where('booking_id', $booking_id)->first();
            if (empty($ticketExists)) {
                return $this->response->setJSON([
                    'status' => "fail",
                    'response' => 404,
                    'message' => "Ticket not found"
                ]);
            }

            // Get websettings for logotext (needed for the view)
            $websetting = $this->webSettingModel->first();
            if ($websetting) {
                // Set logotext in session so the view can access it
                $this->session->set('logotext', $websetting->logotext ?? '');
            }

            // Use Ticketinvoice controller to build ticket data
            $ticketInvoiceController = new \Modules\Ticket\Controllers\Ticketinvoice();
            
            // Build ticket data using reflection to access private method
            $reflection = new \ReflectionClass($ticketInvoiceController);
            $method = $reflection->getMethod('buildTicketData');
            $method->setAccessible(true);
            $data = $method->invoke($ticketInvoiceController, $booking_id);

            // Set invocount for the view (without incrementing print count)
            $countprint = $data['ticket']->count_print ?? 0;
            if ($countprint == 0 || $countprint == 1) {
                $data['invocount'] = "";
            } else {
                // Convert number to words for display
                $numberToWordsMethod = $reflection->getMethod('numberToWords');
                $numberToWordsMethod->setAccessible(true);
                $string_number = $numberToWordsMethod->invoke($ticketInvoiceController, $countprint);
                $data['invocount'] = $string_number . " time print";
            }

            // Get view path
            $viewPath = "Modules\Ticket\Views";
            $viewFile = $viewPath . '\invoice\print';

            // Render view to HTML with output buffering to capture everything
            ob_start();
            echo view($viewFile, $data);
            $html = ob_get_clean();

            // Check if DomPDF is available
            if (!class_exists('\Dompdf\Dompdf')) {
                // If DomPDF is not available, try to use it anyway (it might be autoloaded)
                // Or we can suggest installation
                return $this->response->setJSON([
                    'status' => "fail",
                    'response' => 500,
                    'message' => "PDF library not found. Please install DomPDF: composer require dompdf/dompdf"
                ]);
            }

            // Add Arabic font support and RTL styling to HTML
            // Note: DomPDF has limited Arabic support with DejaVu fonts
            // For better Arabic support, consider using TCPDF or mPDF
            $arabicFontStyle = '
            <style>
                @charset "UTF-8";
                body, html {
                    font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
                    direction: ltr;
                }
                [dir="rtl"], .rtl, *[lang="ar"], *[lang="ar"] * {
                    direction: rtl !important;
                    text-align: right !important;
                    unicode-bidi: embed;
                }
                /* Ensure Arabic text renders properly */
                * {
                    unicode-bidi: plaintext;
                }
            </style>';
            
            // Inject Arabic font style after <head> tag
            $html = str_replace('</head>', $arabicFontStyle . '</head>', $html);
            
            // Ensure UTF-8 encoding meta tag is present (don't convert encoding as it breaks Arabic)
            if (stripos($html, '<meta charset') === false && stripos($html, 'charset=') === false) {
                $html = str_replace('<head>', '<head><meta charset="UTF-8">', $html);
            }
            
            // Ensure the HTML declaration includes UTF-8
            if (stripos($html, '<?xml') === false) {
                $html = '<?xml encoding="UTF-8">' . $html;
            }

            // Generate PDF using DomPDF with proper configuration
            $dompdf = new \Dompdf\Dompdf();
            
            // Configure DomPDF options to handle remote resources and Unicode
            $options = $dompdf->getOptions();
            $options->set('isRemoteEnabled', true);
            $options->set('isHtml5ParserEnabled', true);
            $options->set('isFontSubsettingEnabled', true);
            $options->set('defaultFont', 'DejaVu Sans');
            $options->set('isPhpEnabled', false);
            $options->set('chroot', [ROOTPATH . 'public', ROOTPATH]);
            // Enable Unicode/UTF-8 support
            $options->set('defaultMediaType', 'screen');
            $options->set('isJavascriptEnabled', false);
            $dompdf->setOptions($options);
            
            // Convert relative URLs to absolute URLs for assets
            $baseUrl = base_url();
            $html = preg_replace('/href=["\']([^"\']*public\/[^"\']*)["\']/', 'href="' . $baseUrl . '/$1"', $html);
            $html = preg_replace('/src=["\']([^"\']*public\/[^"\']*)["\']/', 'src="' . $baseUrl . '/$1"', $html);
            // Also handle base_url() calls in the HTML
            $html = str_replace("base_url('public/", "base_url() . '/public/", $html);
            $html = str_replace('base_url("public/', 'base_url() . "/public/', $html);
            
            // Load HTML with explicit UTF-8 encoding
            $dompdf->loadHtml($html, 'UTF-8');
            $dompdf->setPaper('A4', 'portrait');
            
            // Render PDF
            $dompdf->render();
            
            // Note: DomPDF has limited Arabic font support. For full Arabic support,
            // you may need to:
            // 1. Install an Arabic font (like Noto Sans Arabic) and configure it in DomPDF
            // 2. Or use a different PDF library like TCPDF or mPDF which has better Arabic support

            // Create directory if it doesn't exist
            $pdfDir = ROOTPATH . 'public/pdf/tickets/';
            if (!is_dir($pdfDir)) {
                mkdir($pdfDir, 0755, true);
            }

            // Generate unique filename
            $filename = 'ticket_' . $booking_id . '_' . date('YmdHis') . '.pdf';
            $filepath = $pdfDir . $filename;

            // Save PDF file
            file_put_contents($filepath, $dompdf->output());

            // Generate download URL (include /public/ in the path)
            $downloadUrl = base_url() . '/public/pdf/tickets/' . $filename;

            return $this->response->setJSON([
                'status' => "success",
                'response' => 200,
                'message' => "PDF generated successfully",
                'data' => [
                    'booking_id' => $booking_id,
                    'pdf_path' => $downloadUrl,
                    'filename' => $filename
                ]
            ]);

        } catch (\Throwable $e) {
            return $this->response->setJSON([
                'status' => "fail",
                'response' => 500,
                'message' => "Error generating PDF: " . $e->getMessage()
            ]);
        }
    }



    /**
     * Seat inventory for API seat map / JSON layout (overlap + same-bus + temp + admin holds).
     *
     * @return array{booked: list<string>, hold: list<string>, block: list<string>, boarding_date: string, bus_start_date: string}
     */
    protected function collectApiSeatInventory(
        int $subTripId,
        string $journeyDateParam,
        ?string $ignoreTempToken = null,
        ?int $pickStandIdOverride = null
    ): array {
        BookingInventorySupport::syncBookingTimezone();
        $empty = ['booked' => [], 'hold' => [], 'block' => [], 'boarding_date' => '', 'bus_start_date' => ''];

        $subtripInformation = $this->subtripModel->find($subTripId);
        if (! $subtripInformation) {
            return $empty;
        }

        $this->cleanExpiredTemporaryBooks($ignoreTempToken);

        $pickStandId = $pickStandIdOverride ?? (int) (
            $this->request->getVar('pickstand')
            ?? $this->request->getVar('pickstand_id')
            ?? 0
        );
        $mainTripId  = (int) $subtripInformation->trip_id;
        $overnightRun = new \App\Libraries\OvernightTripRunSupport();
        if ($pickStandId <= 0 && $overnightRun->clientMustSelectPickstand($mainTripId, $subTripId)) {
            return $empty;
        }
        if ($pickStandId <= 0) {
            $pickStandId = BookingInventorySupport::resolveDefaultPickStandId($mainTripId, $subTripId);
        }
        $departureDates = BookingInventorySupport::resolveSearchDepartureDates(
            $mainTripId,
            $subTripId,
            $journeyDateParam,
            $pickStandId
        );
        $busStart = $departureDates['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, $mainTripId, $departureDates);
        foreach ($unpaidBuilder->get()->getResult() as $delvalue) {
            if (! BookingInventorySupport::ticketBelongsToBusRun(
                $mainTripId,
                (int) ($delvalue->subtrip_id ?? 0) ?: $subTripId,
                (string) ($delvalue->journeydata ?? ''),
                (int) ($delvalue->pick_stand_id ?? 0),
                $busStart
            )) {
                continue;
            }

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

        $inventory = (new SegmentSeatInventoryService($this->db))->collectSeatBuckets(
            $subTripId,
            $journeyDateParam,
            $pickStandId,
            $ignoreTempToken,
            true
        );

        return [
            'booked' => $inventory['booked'],
            'hold' => $inventory['hold'],
            'block' => $inventory['block'],
            'boarding_date' => $inventory['boarding_date'],
            'bus_start_date' => $inventory['bus_start_date'],
        ];
    }

    /**
     * Resolve pickstand context for API seat-map endpoints (matches admin getSingleTrip deferral).
     *
     * @return array{
     *   main_trip_id: int,
     *   requires_pickstand: bool,
     *   eligible_pick_stands: list<int>,
     *   awaiting_pickstand: bool,
     *   pick_stand_id: int
     * }
     */
    protected function resolveBoardingDateHintFromRequest(): ?string
    {
        $raw = $this->request->getVar('boarding_date') ?? $this->request->getVar('boardingDate');
        if (! is_string($raw) || trim($raw) === '') {
            return null;
        }

        return BookingInventorySupport::normalizeCalendarDate($raw);
    }

    protected function resolveApiPickstandSeatContext(int $subTripId, ?string $boardingSearchDate = null): array
    {
        $empty = [
            'main_trip_id' => 0,
            'requires_pickstand' => false,
            'eligible_pick_stands' => [],
            'awaiting_pickstand' => false,
            'pick_stand_id' => 0,
        ];

        $subtrip = $this->subtripModel->find($subTripId);
        if (! $subtrip) {
            return $empty;
        }

        $mainTripId    = (int) $subtrip->trip_id;
        $overnightRun  = new \App\Libraries\OvernightTripRunSupport();
        $requiresPick  = $overnightRun->clientMustSelectPickstand($mainTripId, $subTripId);
        $pickStandId   = (int) ($this->request->getVar('pickstand') ?? $this->request->getVar('pickstand_id') ?? 0);
        $eligiblePicks = [];

        if ($boardingSearchDate !== null && trim($boardingSearchDate) !== '') {
            $eligiblePicks = $overnightRun->pickStandsForPickupPicker(
                $mainTripId,
                $subTripId,
                BookingInventorySupport::normalizeCalendarDate($boardingSearchDate)
            );
        }

        $awaitingPickstand = $requiresPick && $pickStandId <= 0;

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

        return [
            'main_trip_id' => $mainTripId,
            'requires_pickstand' => $requiresPick,
            'eligible_pick_stands' => $eligiblePicks,
            'awaiting_pickstand' => $awaitingPickstand,
            'pick_stand_id' => $pickStandId,
        ];
    }

    /**
     * Seat map deferred until the client sends pickstand (overnight / multi-day boarding).
     */
    protected function buildDeferredPickstandSeatResponse(array $context): \CodeIgniter\HTTP\ResponseInterface
    {
        $msg = admin_bilingual_lang_strings('Localize.key_select_boarding_point_to_load_seats');

        return $this->response->setJSON([
            'status' => 'success',
            'response' => 200,
            'requires_pickstand' => true,
            'eligible_pick_stands' => $context['eligible_pick_stands'],
            'message' => $msg['en'],
            'message_ar' => $msg['ar'],
            'layout' => [],
            'seatlayout' => [],
            'totalseat' => 0,
        ]);
    }

    /**
     * Overnight trips require an explicit pickstand for booking/hold validation endpoints.
     */
    protected function rejectIfPickstandRequiredForInventory(int $subTripId): ?\CodeIgniter\HTTP\ResponseInterface
    {
        $subtrip = $this->subtripModel->find($subTripId);
        if (! $subtrip) {
            return null;
        }

        $pickStandId  = (int) ($this->request->getVar('pickstand') ?? 0);
        $overnightRun = new \App\Libraries\OvernightTripRunSupport();
        $message      = $overnightRun->validatePickStandForSeatInventory(
            (int) $subtrip->trip_id,
            $subTripId,
            $pickStandId
        );
        if ($message === null) {
            return null;
        }

        return $this->response->setJSON([
            'status' => 'fail',
            'response' => 422,
            'message' => $message,
        ]);
    }

    protected function cleanExpiredTemporaryBooks(?string $preserveToken = null): void
    {
        unset($preserveToken);
        (new TemporaryBookHoldService())->cleanupExpiredHolds();
    }

    /**
     * Debug hints for checkout 409s (safe for production — no PII).
     *
     * @param list<string> $conflictSeats
     * @param list<string> $unavailable
     * @return array<string,mixed>
     */
    protected function buildSeatConflictDebugPayload(
        array $conflictSeats,
        ?string $resolvedToken,
        int $subtripId,
        int $tripId,
        string $busStartDate,
        string $boardingDate,
        int $pickStandId,
        array $unavailable
    ): array {
        $departureDates = BookingInventorySupport::resolveDepartureDates(
            $tripId,
            $subtripId,
            $busStartDate,
            $boardingDate,
            $pickStandId
        );

        return [
            'conflict_seats' => array_values($conflictSeats),
            'resolved_hold_token' => $resolvedToken !== null && $resolvedToken !== '' ? 'yes' : 'no',
            'request_had_ticket_token' => $this->resolveTicketTokenFromRequest() !== null ? 'yes' : 'no',
            'active_temp_hold_for_seats' => $this->findActiveTempHoldRowsForSeats($subtripId, $conflictSeats, $departureDates),
            'bus_start_date' => $busStartDate,
            'boarding_date' => $boardingDate,
            'unavailable_count' => count($unavailable),
            'hint' => 'Call checkSeats before payment-process with the same ticket_token, journey_date, pickstand, and seat_names. Hold must still be active.',
        ];
    }

    /**
     * @param list<string> $seats
     * @param array{bus_start: string, boarding: string, all: list<string>} $departureDates
     * @return list<array<string,mixed>>
     */
    protected function findActiveTempHoldRowsForSeats(int $subtripId, array $seats, array $departureDates): array
    {
        if ($subtripId <= 0 || empty($seats)) {
            return [];
        }

        $normalized = $this->normalizeSeatListForCompare($seats);
        $this->db = \Config\Database::connect();
        (new TemporaryBookHoldService())->cleanupExpiredHolds($this->db);

        $fetch = static function ($builder) {
            return $builder->get()->getResultArray();
        };

        $builder = $this->db->table('temporarybooks')
            ->select('id, ticket_token, seat_names, journey_date, expires_at, created_at')
            ->where('subtrip_id', $subtripId);
        TemporaryBookHoldService::applyActiveHoldExpiryFilter($builder);
        BookingInventorySupport::applyTemporaryBookJourneyDateFilter($builder, $departureDates['all']);
        $rows = $fetch($builder);

        if (empty($rows)) {
            $builder = $this->db->table('temporarybooks')
                ->select('id, ticket_token, seat_names, journey_date, expires_at, created_at')
                ->where('subtrip_id', $subtripId);
            TemporaryBookHoldService::applyActiveHoldExpiryFilter($builder);
            $rows = $fetch($builder);
        }

        $out = [];
        foreach ($rows as $row) {
            $rowSeats = array_values(array_filter(array_map('trim', explode(',', (string) ($row['seat_names'] ?? '')))));
            if ($this->normalizeSeatListForCompare($rowSeats) !== $normalized) {
                continue;
            }
            $out[] = [
                'id' => (int) ($row['id'] ?? 0),
                'journey_date' => (string) ($row['journey_date'] ?? ''),
                'expires_at' => (string) ($row['expires_at'] ?? ''),
                'has_token' => trim((string) ($row['ticket_token'] ?? '')) !== '',
            ];
        }

        return $out;
    }

    /**
     * Seat-hold token from checkSeats (JSON body, POST fields, cookie, or session).
     */
    protected function resolveTicketTokenFromRequest(): ?string
    {
        $json = $this->request->getJSON(true);
        $jsonToken = null;
        if (is_array($json)) {
            $jsonToken = $json['ticket_token'] ?? $json['ticketToken'] ?? null;
        }

        $candidates = [
            $jsonToken,
            $this->request->getVar('ticket_token'),
            $this->request->getVar('ticketToken'),
            $this->request->getCookie('_chkseats_token'),
        ];
        $sessionToken = session('ticket_token');
        if ($sessionToken !== null && $sessionToken !== '') {
            $candidates[] = $sessionToken;
        }
        foreach ($candidates as $candidate) {
            $token = trim((string) ($candidate ?? ''));
            if ($token !== '') {
                return $token;
            }
        }

        return null;
    }

    /**
     * Token for the current checkout: request first, then a unique matching temporarybooks row.
     */
    protected function resolveSeatHoldTokenForPayment(
        int $subtripId,
        array $requestedSeats,
        int $tripId,
        string $busStartDate,
        ?string $boardingDate,
        int $pickStandId
    ): ?string {
        $departureDates = BookingInventorySupport::resolveDepartureDates(
            $tripId,
            $subtripId,
            $busStartDate,
            $boardingDate,
            $pickStandId
        );

        $requestToken = $this->resolveTicketTokenFromRequest();
        if ($requestToken !== null && $this->tempHoldMatchesToken($subtripId, $requestedSeats, $departureDates, $requestToken)) {
            return $requestToken;
        }

        $inferred = $this->inferTempHoldTokenForBooking($subtripId, $requestedSeats, $departureDates);
        if ($inferred !== null) {
            return $inferred;
        }

        return $requestToken;
    }

    /**
     * True when an active temporarybooks row exists for this token + subtrip + seats + journey dates.
     */
    protected function tempHoldMatchesToken(int $subtripId, array $requestedSeats, array $departureDates, string $token): bool
    {
        if ($subtripId <= 0 || trim($token) === '') {
            return false;
        }

        $normalizedSeatList = $this->normalizeSeatListForCompare($requestedSeats);
        if ($normalizedSeatList === '') {
            return false;
        }
        $row = $this->findLatestMatchingTempHoldRow($subtripId, $normalizedSeatList, $departureDates, true);
        if ($row === null) {
            $row = $this->findLatestMatchingTempHoldRow($subtripId, $normalizedSeatList, $departureDates, false);
        }

        if ($row === null) {
            return false;
        }

        return trim((string) ($row['ticket_token'] ?? '')) === trim($token);
    }

    /**
     * When the website omits ticket_token on payment-process, match the active temp hold for these seats.
     */
    protected function inferTempHoldTokenForBooking(int $subtripId, array $requestedSeats, array $departureDates): ?string
    {
        if ($subtripId <= 0 || empty($requestedSeats)) {
            return null;
        }

        $normalizedRequested = $this->normalizeSeatListForCompare($requestedSeats);
        if ($normalizedRequested === '') {
            return null;
        }

        $row = $this->findLatestMatchingTempHoldRow($subtripId, $normalizedRequested, $departureDates, true);
        if ($row === null) {
            $row = $this->findLatestMatchingTempHoldRow($subtripId, $normalizedRequested, $departureDates, false);
        }

        if ($row === null) {
            return null;
        }

        $token = trim((string) ($row['ticket_token'] ?? ''));

        return $token !== '' ? $token : null;
    }

    /**
     * @param array{bus_start: string, boarding: string, all: list<string>} $departureDates
     * @return array<string,mixed>|null
     */
    protected function findLatestMatchingTempHoldRow(
        int $subtripId,
        string $normalizedSeatList,
        array $departureDates,
        bool $useJourneyFilter
    ): ?array {
        $this->db = \Config\Database::connect();
        $builder = $this->db->table('temporarybooks');
        $builder->select('ticket_token, seat_names, created_at')
            ->where('subtrip_id', $subtripId);
        TemporaryBookHoldService::applyActiveHoldExpiryFilter($builder);
        $builder->orderBy('created_at', 'DESC');

        if ($useJourneyFilter) {
            BookingInventorySupport::applyTemporaryBookJourneyDateFilter($builder, $departureDates['all']);
        }

        foreach ($builder->get()->getResultArray() as $row) {
            $seats = array_values(array_filter(array_map('trim', explode(',', (string) ($row['seat_names'] ?? '')))));
            if ($this->normalizeSeatListForCompare($seats) === $normalizedSeatList) {
                return $row;
            }
        }

        return null;
    }

    protected function normalizeSeatListForCompare(array $seats): string
    {
        $seats = array_values(array_filter(array_map('trim', $seats)));
        sort($seats, SORT_NATURAL);

        return implode(',', $seats);
    }

    /**
     * Admin-held seats for this subtrip that overlap the requested segment on the main route.
     *
     * @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);
        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 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))->collectUnavailableSeatNumbers(
            (int) $subtripid,
            $journeyParam,
            (int) $pickStandId,
            $ignoreToken,
            $pickup,
            $drop
        );
    }

    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'
        );
    }

    /**
     * @param list<string> $seatNumbers
     * @return array{error?: array<string, mixed>, expected?: array<string, mixed>|null}
     */
    private function runOneWayOfferFareCheck(
        int $tripId,
        int $subtripId,
        array $seatNumbers,
        ?string $offerCode,
        float $clientGrandTotal,
        ?float $clientDiscount,
        float $tax
    ): array {
        try {
            $result = BookingOfferFareService::validateOneWayLeg(
                $tripId,
                $subtripId,
                $seatNumbers,
                $offerCode,
                $clientGrandTotal,
                $clientDiscount,
                $tax
            );
        } catch (\InvalidArgumentException $e) {
            return [
                'error' => [
                    'message' => $e->getMessage(),
                    'status' => 'fail',
                    'response' => 422,
                    'data' => null,
                ],
            ];
        }

        if (! ($result['ok'] ?? false)) {
            return [
                'error' => [
                    'message' => $result['message'] ?? 'Fare validation failed.',
                    'status' => 'fail',
                    'response' => 422,
                    'data' => $result['expected'] ?? null,
                ],
            ];
        }

        return ['expected' => $result['expected'] ?? null];
    }

    /**
     * @param list<string> $outSeats
     * @param list<string> $returnSeats
     * @return array{error?: array<string, mixed>, expected?: array<string, mixed>|null}
     */
    private function runRoundTripOfferFareCheck(
        int $outTripId,
        int $outSubtripId,
        array $outSeats,
        float $outBaseFare,
        int $returnTripId,
        int $returnSubtripId,
        array $returnSeats,
        float $returnBaseFare,
        ?string $offerCode,
        float $clientGrandTotal,
        ?float $clientDiscount,
        float $tax,
        float $roundTripDiscountPercent
    ): array {
        try {
            $result = BookingOfferFareService::validateRoundTrip(
                $outTripId,
                $outSubtripId,
                $outSeats,
                $outBaseFare,
                $returnTripId,
                $returnSubtripId,
                $returnSeats,
                $returnBaseFare,
                $offerCode,
                $clientGrandTotal,
                $clientDiscount,
                $tax,
                $roundTripDiscountPercent
            );
        } catch (\InvalidArgumentException $e) {
            return [
                'error' => [
                    'message' => $e->getMessage(),
                    'status' => 'fail',
                    'response' => 422,
                    'data' => null,
                ],
            ];
        }

        if (! ($result['ok'] ?? false)) {
            return [
                'error' => [
                    'message' => $result['message'] ?? 'Fare validation failed.',
                    'status' => 'fail',
                    'response' => 422,
                    'data' => $result['expected'] ?? null,
                ],
            ];
        }

        return ['expected' => $result['expected'] ?? null];
    }

    private function resolveGeideaSessionLanguage(): string
    {
        $lang = strtolower(trim((string) ($this->request->getVar('language') ?? '')));

        return in_array($lang, ['ar', 'en'], true) ? $lang : 'en';
    }

    /**
     * @return array{user_id?: int, error?: array<string, mixed>}
     */
    private function resolvePassengerForApi(?string $loginEmail, ?string $loginMobile, $explicitUserId = null): array
    {
        if ($staffError = BookingPassengerSupport::staffMobileError($this->userModel, $loginMobile)) {
            return [
                'error' => [
                    'message' => $staffError,
                    'status' => 'fail',
                    'response' => 422,
                    'data' => null,
                ],
            ];
        }

        $userId = (! empty($explicitUserId) && is_numeric($explicitUserId))
            ? (int) $explicitUserId
            : $this->userCheck((string) ($loginEmail ?? ''), (string) ($loginMobile ?? ''));

        if (empty($userId)) {
            return [
                'error' => [
                    'message' => 'User check fail',
                    'status' => 'fail',
                    'response' => 404,
                    'data' => 'user check error',
                ],
            ];
        }

        if ($roleError = BookingPassengerSupport::nonPassengerRoleError($this->userModel, (int) $userId)) {
            return [
                'error' => [
                    'message' => $roleError,
                    'status' => 'fail',
                    'response' => 422,
                    'data' => null,
                ],
            ];
        }

        BookingPassengerSupport::ensurePassengerUserDetails(
            $this->userDetailModel,
            (int) $userId,
            $this->passengerProfileFromRequest()
        );

        return ['user_id' => (int) $userId];
    }

    /**
     * @return array<string, mixed>
     */
    private function passengerProfileFromRequest(): array
    {
        return [
            'first_name' => $this->request->getVar('first_name'),
            '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,
        ];
    }
}

