<?php

namespace FluentBooking\App\Hooks\Handlers;

use FluentBooking\App\App;
use FluentBooking\App\Models\Booking;
use FluentBooking\App\Models\Calendar;
use FluentBooking\App\Models\CalendarSlot;
use FluentBooking\App\Services\BookingFieldService;
use FluentBooking\App\Services\BookingService;
use FluentBooking\App\Services\DateTimeHelper;
use FluentBooking\App\Services\Helper;
use FluentBooking\App\Services\Integrations\PaymentMethods\CurrenciesHelper;
use FluentBooking\App\Services\LandingPage\LandingPageHandler;
use FluentBooking\App\Services\LocationService;
use FluentBooking\App\Services\ReceiptHelper;
use FluentBooking\App\Services\TimeSlotService;
use FluentBooking\App\Services\PermissionManager;
use FluentBooking\Framework\Support\Arr;
use FluentBooking\Framework\Support\Collection;
use FluentBooking\Framework\Validator\ValidationException;

class FrontEndHandler
{
    public function register()
    {
        add_shortcode('fluent_booking', [$this, 'handleBookingShortcode']);

        add_shortcode('fluent_booking_team', [$this, 'handleTeamShortcode']);

        add_shortcode('fluent_booking_receipt', [$this, 'handleReceiptShortcode']);

        add_action('wp_ajax_fluent_cal_schedule_meeting', [$this, 'ajaxScheduleMeeting']);
        add_action('wp_ajax_nopriv_fluent_cal_schedule_meeting', [$this, 'ajaxScheduleMeeting']);

        add_action('wp_ajax_fcal_cancel_meeting', [$this, 'ajaxHandleCancelMeeting']);
        add_action('wp_ajax_nopriv_fcal_cancel_meeting', [$this, 'ajaxHandleCancelMeeting']);

        add_action('wp_ajax_fluent_cal_get_available_dates', [$this, 'ajaxGetAvailableDates']);
        add_action('wp_ajax_nopriv_fluent_cal_get_available_dates', [$this, 'ajaxGetAvailableDates']);

        /*
         * Rescheduing Handlers
         */
        add_action('fluent_booking/starting_scheduling_ajax', function ($data) {
            if (empty($data['rescheduling_hash'])) {
                return;
            }

            add_filter('fluent_booking/schedule_custom_field_data', function ($array) {
                return [];
            });

            add_filter('fluent_booking/schedule_validation_rules_data', function ($data) {
                return [
                    'messages' => [
                        '_rescheduling_reason.required' => __('Please provide a rescheduling reason', 'fluent-booking-pro')
                    ],
                    'rules'    => [
                        '_rescheduling_reason' => 'required'
                    ]
                ];
            });

            add_action('fluent_booking/before_creating_schedule', function ($bookingData, $postedData, $calendarEvent) {
                $existingHash = Arr::get($postedData, 'rescheduling_hash');
                $existingBooking = Booking::where('hash', $existingHash)->first();

                if (!$existingBooking) {
                    wp_send_json([
                        'message' => __('Invalid rescheduling request', 'fluent-booking-pro')
                    ], 422);
                }

                if (!$existingBooking->canReschedule()) {
                    wp_send_json([
                        'message' => __('Sorry, you can not reschedule this meeting.', 'fluent-booking-pro')
                    ], 422);
                }

                if ($bookingData['start_time'] == $existingBooking->start_time) {
                    wp_send_json([
                        'message' => __('Sorry, you can not reschedule to the same time.', 'fluent-booking-pro')
                    ], 422);
                }

                $endDateTime = gmdate('Y-m-d H:i:s', strtotime($bookingData['start_time']) + ($existingBooking->slot_minutes * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date

                $previousBooking = clone $existingBooking;

                if ($existingBooking->event_type == 'group') {
                    // Need to handle group booking type here
                    // check for existing group
                    $parent = Booking::where('status', 'scheduled')
                        ->where('event_id', $existingBooking->event_id)
                        ->where('start_time', $bookingData['start_time'])
                        ->orderBy('id', 'ASC')
                        ->first();

                    if ($parent) {
                        $existingBooking->group_id = $parent->group_id;
                    } else {
                        $existingBooking->group_id = Helper::getNextBookingGroup();
                    }
                }

                $existingBooking->start_time = $bookingData['start_time'];
                $existingBooking->person_time_zone = $bookingData['person_time_zone'];
                $existingBooking->end_time = $endDateTime;
                $existingBooking->save();

                $reschedulingMessage = sanitize_textarea_field(Arr::get($postedData, '_rescheduling_reason'));
                $existingBooking->updateMeta('reschedule_reason', $reschedulingMessage);
                $existingBooking->updateMeta('previous_meeting_time', $previousBooking->start_time);

                $rescheduleBy = 'guest';
                if ($existingBooking->host_user_id == get_current_user_id() || PermissionManager::userCan('manage_all_bookings')) {
                    $rescheduleBy = 'host';
                }

                $existingBooking->updateMeta('rescheduled_by_type', $rescheduleBy);

                do_action('fluent_booking/log_booking_activity', [
                    'booking_id'  => $existingBooking->id,
                    'type'        => 'info',
                    'status'      => 'closed',
                    'title'       => __('Meeting Rescheduled', 'fluent-booking-pro'),
                    'description' => __(sprintf('Meeting has been rescheduled by %1s from Web UI. Previous date time: %2s (UTC)', $rescheduleBy, $previousBooking->start_time), 'fluent-booking-pro')
                ]);

                do_action('fluent_booking/after_booking_rescheduled', $existingBooking, $previousBooking);

                add_filter('fluent_booking/schedule_receipt_data', function ($data) {
                    $data['title'] = __('Your meeting has been rescheduled', 'fluent-booking-pro');
                    return $data;
                });

                $redirectUrl = $calendarEvent->getRedirectUrlWithQuery($existingBooking);

                $html = BookingService::getBookingConfirmationHtml($existingBooking);

                wp_send_json([
                    'message'       => __('Booking has been rescheduled', 'fluent-booking-pro'),
                    'redirect_url'  => $redirectUrl,
                    'response_html' => $html,
                    'booking_hash'  => $existingBooking->hash
                ], 200);

            }, 10, 3);
        });
    }

    public function handleBookingShortcode($atts, $content)
    {
        $atts = shortcode_atts([
            'id'             => 0,
            'disable_author' => 'no',
            'theme'          => 'light'
        ], $atts);

        if (!$atts['id']) {
            return '';
        }

        $calendarEvent = CalendarSlot::query()->find($atts['id']);

        if (!$calendarEvent) {
            return '';
        }

        $calendar = $calendarEvent->calendar;

        if (!$calendar) {
            return __('Calendar not found', 'fluent-booking-pro');
        }

        $assetUrl = App::getInstance('url.assets');

        $localizeData = $this->getCalendarEventVars($calendar, $calendarEvent);
        $localizeData['disable_author'] = $atts['disable_author'] == 'yes';
        $localizeData['theme'] = $atts['theme'];

        if (BookingFieldService::hasPhoneNumberField($localizeData['form_fields'])) {
            wp_enqueue_script('fluent-booking-phone-field', App::getInstance('url.assets') . 'public/js/phone-field.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
            add_action('fluent_booking/short_code_render', function () use ($assetUrl) {
                ?>
                <style>
                    .fcal_phone_wrapper .flag {
                        background: url(<?php echo esc_url($assetUrl.'images/flags_responsive.png'); ?>) no-repeat;
                        background-size: 100%;
                    }
                </style>
                <?php
            });
        }

        wp_enqueue_script('fluent-booking-public', $assetUrl . 'public/js/app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);

        $this->loadGlobalVars();
        wp_localize_script(
            'fluent-booking-public',
            'fcal_public_vars_' . $calendar->id . '_' . $calendarEvent->id,
            $localizeData,
        );

        return App::make('view')->make('public.calendar', [
            'calenderEvent' => $calendarEvent,
            'theme'         => $atts['theme']
        ]);
    }

    public function handleTeamShortcode($atts, $content)
    {
        $atts = shortcode_atts([
            'event_ids'   => '',
            'title'       => '',
            'description' => '',
            'logo_url'    => ''
        ], $atts);

        if (!$atts['event_ids']) {
            return '';
        }

        $eventIds = array_filter(array_map('intval', explode(',', $atts['event_ids'])));

        if (empty($eventIds)) {
            return '';
        }

        $events = CalendarSlot::query()->whereIn('id', $eventIds)
            ->where('status', 'active')
            ->get();

        $calendarIds = [];
        $calendarEvents = [];

        foreach ($events as $event) {
            $calendarIds[] = $event->calendar_id;
            if (!isset($calendarEvents[$event->calendar_id])) {
                $calendarEvents[$event->calendar_id] = [];
            }
            $calendarEvents[$event->calendar_id][] = $event;
        }

        $calendars = Calendar::query()->whereIn('id', $calendarIds)->get();

        foreach ($calendars as $calendar) {
            $calendar->activeEvents = $calendarEvents[$calendar->id];
        }

        return $this->renderTeamHosts($calendars, [
            'title'         => $atts['title'],
            'description'   => $atts['description'],
            'logo'          => $atts['logo_url'],
            'wrapper_class' => ''
        ]);
    }

    public function renderTeamHosts($calendars, $headerConfig = [])
    {
        $wrapperId = 'fcal_team_' . Helper::getNextIndex();
        wp_enqueue_script('fluent-booking-team', App::getInstance('url.assets') . 'public/js/team_app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);

        $vars = [];
        foreach ($calendars as $calendar) {

            $hostHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
                'author'   => $calendar->getAuthorProfile(),
                'calendar' => $calendar,
                'events'   => $calendar->activeEvents
            ]);

            $hostHtml .= '<div onclick="fcalBackToTeam(this)" class="fcal_back_btn_team"><svg height="20px" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><polygon points="352,128.4 319.7,96 160,256 160,256 160,256 319.7,416 352,383.6 224.7,256 "></polygon></svg> <span>' . __('Back to team', 'fluent-booking-pro') . '</span></div>';

            $eventCount = count($calendar->activeEvents);

            $vars['fcal_host_' . $calendar->id] = [
                'host_html'       => $hostHtml,
                'event_count'     => $eventCount,
                'target_event_id' => ($eventCount == 1) ? $calendar->activeEvents[0]->id : 0
            ];


            foreach ($calendar->activeEvents as $event) {
                $itemVars = $this->getCalendarEventVars($event->calendar, $event);
                $extraJs = (new LandingPageHandler())->getEventLandingExtraJsFiles($itemVars['form_fields'], $event);
                if ($extraJs) {
                    $itemVars['lazy_js_files'] = $extraJs;
                }
                wp_localize_script('fluent-booking-team', 'fcal_public_vars_' . $event->calendar_id . '_' . $event->id, $itemVars);
            }
        }

        wp_localize_script('fluent-booking-team', $wrapperId, $vars);

        $assetUrl = App::getInstance('url.assets');
        wp_enqueue_script('fluent-booking-public', $assetUrl . 'public/js/app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
        $this->loadGlobalVars();

        return App::make('view')->make('public.team_page', [
            'hosts'         => $calendars,
            'wrapper_id'    => $wrapperId,
            'logo'          => Arr::get($headerConfig, 'logo', ''),
            'title'         => Arr::get($headerConfig, 'title', ''),
            'description'   => Arr::get($headerConfig, 'description', ''),
            'wrapper_class' => Arr::get($headerConfig, 'wrapper_class', '')
        ]);
    }

    public function handleReceiptShortcode($atts, $content)
    {
        if (!isset($_REQUEST['hash'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            return __('Booking hash is missing!', 'fluent-booking-pro');
        }

        $hash = sanitize_text_field($_REQUEST['hash']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended

        return (new ReceiptHelper())->getReceipt($hash);
    }

    private function loadGlobalVars()
    {
        static $loaded;

        if ($loaded) {
            return;
        }

        $loaded = true;

        wp_localize_script('fluent-booking-public', 'fluentCalendarPublicVars', $this->getGlobalVars());
    }

    public function getGlobalVars()
    {
        $currentPerson = [
            'name'  => '',
            'email' => ''
        ];

        if (is_user_logged_in()) {
            $currentUser = wp_get_current_user();
            $name = trim($currentUser->first_name . ' ' . $currentUser->last_name);

            if (!$name) {
                $name = $currentUser->display_name;
            }

            $currentPerson = [
                'name'    => $name,
                'email'   => $currentUser->user_email,
                'user_id' => $currentUser->ID
            ];
        } else {

            $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

            // Check for url params
            if ($name = sanitize_text_field(Arr::get($request, 'invitee_name'))) {
                $currentPerson['name'] = $name;
            }

            if ($email = Arr::get($request, 'invitee_email')) {
                $email = sanitize_email($email);
                if (is_email($email)) {
                    $currentPerson['email'] = $email;
                }
            }
        }

        if (empty($currentPerson['email'])) {
            // Let's try to get from FluentCRM is exists
            if (defined('FLUENTCRM')) {
                $contactApi = FluentCrmApi('contacts');
                $contact = $contactApi->getCurrentContact();
                if ($contact) {
                    $currentPerson['email'] = $contact->email;
                    $currentPerson['name'] = $contact->full_name;
                }
            }
        }

        $globalSettings = Helper::getGlobalSettings();
        $startDay = Arr::get($globalSettings, 'administration.start_day', 'mon');

        $data = [
            'ajaxurl'        => admin_url('admin-ajax.php'),
            'timezones'      => DateTimeHelper::getFlatGroupedTimeZones(),
            'current_person' => $currentPerson,
            'start_day'      => $startDay,
            'i18'            => [
                'Timezone'                      => __('Timezone', 'fluent-booking-pro'),
                'minutes'                       => __('minutes', 'fluent-booking-pro'),
                'Enter Details'                 => __('Enter Details', 'fluent-booking-pro'),
                'Summary'                       => __('Summary', 'fluent-booking-pro'),
                'Payment Details'               => __('Payment Details', 'fluent-booking-pro'),
                'Total Payment'                 => __('Total Payment', 'fluent-booking-pro'),
                'Pay Now'                       => __('Pay Now', 'fluent-booking-pro'),
                'processing'                    => __('Processing', 'fluent-booking-pro'),
                'date_time_config'              => [
                    'weekdays'      => array(
                        'sunday'    => _x('Sunday', 'calendar day full', 'fluent-booking-pro'),
                        'monday'    => _x('Monday', 'calendar day full', 'fluent-booking-pro'),
                        'tuesday'   => _x('Tuesday', 'calendar day full', 'fluent-booking-pro'),
                        'wednesday' => _x('Wednesday', 'calendar day full', 'fluent-booking-pro'),
                        'thursday'  => _x('Thursday', 'calendar day full', 'fluent-booking-pro'),
                        'friday'    => _x('Friday', 'calendar day full', 'fluent-booking-pro'),
                        'saturday'  => _x('Saturday', 'calendar day full', 'fluent-booking-pro'),
                    ),
                    'months'        => array(
                        'January'   => _x('January', 'calendar month name full', 'fluent-booking-pro'),
                        'February'  => _x('February', 'calendar month name full', 'fluent-booking-pro'),
                        'March'     => _x('March', 'calendar month name full', 'fluent-booking-pro'),
                        'April'     => _x('April', 'calendar month name full', 'fluent-booking-pro'),
                        'May'       => _x('May', 'calendar month name full', 'fluent-booking-pro'),
                        'June'      => _x('June', 'calendar month name full', 'fluent-booking-pro'),
                        'July'      => _x('July', 'calendar month name full', 'fluent-booking-pro'),
                        'August'    => _x('August', 'calendar month name full', 'fluent-booking-pro'),
                        'September' => _x('September', 'calendar month name full', 'fluent-booking-pro'),
                        'October'   => _x('October', 'calendar month name full', 'fluent-booking-pro'),
                        'November'  => _x('November', 'calendar month name full', 'fluent-booking-pro'),
                        'December'  => _x('December', 'calendar month name full', 'fluent-booking-pro')
                    ),
                    'weekdaysShort' => array(
                        'sun' => _x('Sun', 'calendar day short', 'fluent-booking-pro'),
                        'mon' => _x('Mon', 'calendar day short', 'fluent-booking-pro'),
                        'tue' => _x('Tue', 'calendar day short', 'fluent-booking-pro'),
                        'wed' => _x('Wed', 'calendar day short', 'fluent-booking-pro'),
                        'thu' => _x('Thu', 'calendar day short', 'fluent-booking-pro'),
                        'fri' => _x('Fri', 'calendar day short', 'fluent-booking-pro'),
                        'sat' => _x('Sat', 'calendar day short', 'fluent-booking-pro')
                    ),
                    'monthsShort'   => array(
                        'jan' => _x('Jan', 'calendar month name short', 'fluent-booking-pro'),
                        'feb' => _x('Feb', 'calendar month name short', 'fluent-booking-pro'),
                        'mar' => _x('Mar', 'calendar month name short', 'fluent-booking-pro'),
                        'apr' => _x('Apr', 'calendar month name short', 'fluent-booking-pro'),
                        'may' => _x('May', 'calendar month name short', 'fluent-booking-pro'),
                        'jun' => _x('Jun', 'calendar month name short', 'fluent-booking-pro'),
                        'jul' => _x('Jul', 'calendar month name short', 'fluent-booking-pro'),
                        'aug' => _x('Aug', 'calendar month name short', 'fluent-booking-pro'),
                        'sep' => _x('Sep', 'calendar month name short', 'fluent-booking-pro'),
                        'oct' => _x('Oct', 'calendar month name short', 'fluent-booking-pro'),
                        'nov' => _x('Nov', 'calendar month name short', 'fluent-booking-pro'),
                        'dec' => _x('Dec', 'calendar month name short', 'fluent-booking-pro')
                    ),
                    'numericSystem' => _x('0_1_2_3_4_5_6_7_8_9', 'calendar numeric system - Sequence must need to maintained', 'fluent-booking-pro'),
                ],
                'Country'                          => __('Country', 'fluent-booking-pro'),
                '12h'                              => _x('12h', 'date time format switch', 'fluent-booking-pro'),
                '24h'                              => _x('24h', 'date time format switch', 'fluent-booking-pro'),
                'spots left'                       => _x('spots left', 'for how many spots left for available booking', 'fluent-booking-pro'),
                'Next'                             => _x('Next', 'Booking form spot selection', 'fluent-booking-pro'),
                'Select on the Next Step'          => __('Select on the Next Step', 'fluent-booking-pro'),
                'location options'                 => __('location options', 'fluent-booking-pro'),
                'Your address'                     => __('Your address', 'fluent-booking-pro'),
                'Organizer Phone Number'           => __('Organizer Phone Number', 'fluent-booking-pro'),
                'In Person (Attendee Address)'     => __('In Person (Attendee Address)', 'fluent-booking-pro'),
                'In Person (Organizer Address)'    => __('In Person (Organizer Address)', 'fluent-booking-pro'),
                'Attendee Phone Number'            => __('Attendee Phone Number', 'fluent-booking-pro'),
                'Google Meet'                      => __('Google Meet', 'fluent-booking-pro'),
                'Zoom Meeting'                     => __('Zoom Meeting', 'fluent-booking-pro'),
                'Online Meeting'                   => __('Online Meeting', 'fluent-booking-pro'),
                'Processing...'                    => __('Processing...', 'fluent-booking-pro'),
                'Loading Payment Processor...'     => __('Loading Payment Processor...', 'fluent-booking-pro'),
                'PM'                               => __('PM', 'fluent-booking-pro'),
                'AM'                               => __('AM', 'fluent-booking-pro'),
                'Email'                            => __('Email', 'fluent-booking-pro'),
                'Date'                             => __('Date', 'fluent-booking-pro'),
                'Time'                             => __('Time', 'fluent-booking-pro'),
                'Add guests'                       => __('Add guests', 'fluent-booking-pro'),
                'Add another'                      => __('Add another', 'fluent-booking-pro'),
                'This field is required.'          => __('This field is required.', 'fluent-booking-pro'),
                'No availability in'               => __('No availability in', 'fluent-booking-pro'),
                'View next month'                  => __('View next month', 'fluent-booking-pro'),
                'View previous month'              => __('View previous month', 'fluent-booking-pro'),
                'Please fill up the required data' => __('Please fill up the required data', 'fluent-booking-pro')
            ],
            'theme'          => Arr::get(get_option('_fluent_booking_settings'), 'theme','system-default')
        ];

        if (isset($_SERVER['HTTP_CF_IPCOUNTRY'])) {
            $data['user_country'] = sanitize_text_field($_SERVER['HTTP_CF_IPCOUNTRY']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        } else {
            $data['user_country'] = Arr::get($globalSettings, 'administration.default_country', '');
        }

        return apply_filters('fluent_calendar/global_booking_vars', $data);
    }

    public function ajaxScheduleMeeting()
    {
        $app = App::getInstance();

        $slotId = (int)$_REQUEST['event_id']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

        $calendarSlot = CalendarSlot::find($slotId);

        if (!$calendarSlot || $calendarSlot->status != 'active') {
            wp_send_json([
                'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking-pro')
            ], 422);
        }

        $postedData = $_REQUEST;  // phpcs:ignore WordPress.Security.NonceVerification.Recommended

        do_action('fluent_booking/starting_scheduling_ajax', $postedData);

        $rules = [
            'name'       => 'required',
            'email'      => 'required|email',
            'timezone'   => 'required',
            'start_date' => 'required'
        ];

        $messages = [
            'name.required'       => __('Please enter your name', 'fluent-booking-pro'),
            'email.required'      => __('Please enter your email address', 'fluent-booking-pro'),
            'email.email'         => __('Please enter provide a valid email address', 'fluent-booking-pro'),
            'timezone.required'   => __('Please select timezone first', 'fluent-booking-pro'),
            'start_date.required' => __('Please select a date and time', 'fluent-booking-pro')
        ];

        if ($calendarSlot->isPhoneRequired()) {
            $rules['phone_number'] = 'required';
            $messages['phone_number.required'] = __('Please provide your phone number', 'fluent-booking-pro');
        } else if ($calendarSlot->isAddressRequired()) {
            $rules['address'] = 'required';
            $messages['address.required'] = __('Please provide your Address', 'fluent-booking-pro');
        } else if ($calendarSlot->isLocationFieldRequired()) {
            $rules['location_config.driver'] = 'required';
            $messages['location_config.driver'] = __('Please select location', 'fluent-booking-pro');

            $selectedLocation = LocationService::getLocationDetails($calendarSlot, Arr::get($postedData, 'location_config', []), $postedData);
            $selectedLocationDriver = Arr::get($selectedLocation, 'type');
            // is user input required
            if (in_array($selectedLocationDriver, ['in_person_guest', 'phone_guest'])) {
                $rules['location_config.user_location_input'] = 'required';
                if ($selectedLocationDriver == 'in_person_guest') {
                    $messages['location_config.user_location_input.required'] = __('Please provide your address', 'fluent-booking-pro');
                } else {
                    $messages['location_config.user_location_input.required'] = __('Please provide your phone number', 'fluent-booking-pro');
                }
            }
        }

        if ($additionalGuests = Arr::get($postedData, 'guests', [])) {
            $postedData['guests'] = array_filter(array_map('sanitize_email', $additionalGuests));
        }

        $requiredFields = array_filter($calendarSlot->getMeta('booking_fields', []), function ($field) {
            return Arr::isTrue($field, 'required') && Arr::isTrue($field, 'enabled') && (Arr::get($field, 'name') == 'message' || Arr::get($field, 'name') == 'guests');
        });

        foreach ($requiredFields as $field) {
            if (empty($rules[$field['name']])) {
                $rules[$field['name']] = 'required';
                $messages[$field['name'] . '.required'] = __('This field is required', 'fluent-booking-pro');
            }
        }

        $validationConfig = apply_filters('fluent_booking/schedule_validation_rules_data', [
            'rules'    => $rules,
            'messages' => $messages
        ], $postedData, $calendarSlot);

        $validator = $app->validator->make($postedData, $validationConfig['rules'], $validationConfig['messages']);
        if ($validator->validate()->fails()) {
            wp_send_json([
                'message' => __('Please fill up the required data', 'fluent-booking-pro'),
                'errors'  => $validator->errors()
            ], 422);
            return;
        }

        $customFieldsData = BookingFieldService::getCustomFieldsData($postedData, $calendarSlot);
        $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $customFieldsData, $calendarSlot);

        if (is_wp_error($customFieldsData)) {
            wp_send_json([
                'message' => $customFieldsData->get_error_message(),
                'errors'  => $customFieldsData->get_error_data()
            ], 422);
            return;
        }

        $timezone = Arr::get($postedData, 'timezone', 'UTC');
        $duration = $calendarSlot->getDuration(Arr::get($_REQUEST, 'duration', null));

        $startDateTime = DateTimeHelper::convertToUtc($postedData['start_date'], $timezone);
        $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date

        $bookingData = [
            'person_time_zone' => sanitize_text_field($timezone),
            'start_time'       => $startDateTime,
            'name'             => sanitize_text_field($postedData['name']),
            'email'            => sanitize_email($postedData['email']),
            'message'          => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),
            'phone'            => sanitize_textarea_field(Arr::get($postedData, 'phone_number', '')),
            'address'          => sanitize_textarea_field(Arr::get($postedData, 'address', '')),
            'ip_address'       => Helper::getIp(),
            'status'           => 'scheduled',
            'source'           => 'web',
            'event_type'       => $calendarSlot->event_type,
            'slot_minutes'     => $duration
        ];

        $selectedLocation = LocationService::getLocationDetails($calendarSlot, Arr::get($postedData, 'location_config', []), $postedData);
        if ($selectedLocation['type'] == 'phone_guest') {
            $bookingData['phone'] = $selectedLocation['description'];
        }

        $bookingData['location_details'] = $selectedLocation;

        if ($sourceUrl = Arr::get($postedData, 'source_url', '')) {
            $bookingData['source_url'] = sanitize_url($sourceUrl);
        }

        if (!empty($postedData['payment_method'])) {
            $customFieldsData['payment_method'] = $postedData['payment_method'];
        }

        if ($additionalGuests) {
            $guestField = BookingFieldService::getBookingFieldByName($calendarSlot, 'guests');
            $guestLimit = Arr::get($guestField, 'limit', 10);
            $bookingData['additional_guests'] = array_slice($additionalGuests, 0, $guestLimit);
        }

        $hostIds = null;
        if ($calendarSlot->isTeamEvent()) {
            $hostIds = $calendarSlot->getHostIdsSortedByBookings($startDateTime);
        }

        // Check if the time is available or not for this slot
        $timeSlotService = new TimeSlotService($calendarSlot->calendar, $calendarSlot);
        $isSpotAvailable = false;

        if ($hostIds) {
            foreach ($hostIds as $hostId) {
                $isSpotAvailable = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration, $hostId);
                if ($isSpotAvailable) {
                    $bookingData['host_user_id'] = $hostId;
                    break;
                }
            }
        } else {
            $isSpotAvailable = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration);
        }

        if (!$isSpotAvailable) {
            wp_send_json([
                'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking-pro')
            ], 422);
        }

        do_action('fluent_booking/before_creating_schedule', $bookingData, $postedData, $calendarSlot);

        try {
            $booking = BookingService::createBooking($bookingData, $calendarSlot, $customFieldsData);

            if (is_wp_error($booking)) {
                throw new \Exception(wp_kses_post($booking->get_error_message()), 422);
            }

        } catch (\Exception $e) {
            wp_send_json([
                'message' => $e->getMessage()
            ], 422);
            return;
        }

        $redirectUrl = $calendarSlot->getRedirectUrlWithQuery($booking);

        $html = BookingService::getBookingConfirmationHtml($booking);

        wp_send_json(apply_filters('fluent_booking/booking_confirmation_response', [
            'message'       => __('Booking has been confirmed', 'fluent-booking-pro'),
            'redirect_url'  => $redirectUrl,
            'response_html' => $html,
            'booking_hash'  => $booking->hash
        ], $booking), 200);
    }

    public function ajaxGetAvailableDates()
    {
        $startBenchmark = microtime(true);
        $slotId = (int)$_REQUEST['event_id']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $slot = CalendarSlot::findOrfail($slotId);

        if (!$slot || $slot->status != 'active') {
            wp_send_json([
                'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking-pro')
            ], 422);
        }

        $calendar = $slot->calendar;
        $startDate = Arr::get($_REQUEST, 'start_date'); // phpcs:ignore WordPress.Security.NonceVerification.Recommended

        if (!$startDate) {
            $startDate = gmdate('Y-m-d H:i:s'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
        }

        $timeZone = Arr::get($_REQUEST, 'timezone'); // phpcs:ignore WordPress.Security.NonceVerification.Recommended

        if (!$timeZone) {
            $timeZone = wp_timezone_string();
        }

        if (!in_array($timeZone, \DateTimeZone::listIdentifiers())) {
            $timeZone = $calendar->author_timezone;
        }

        $duration = $slot->getDuration(Arr::get($_REQUEST, 'duration', null));

        $timeSlotService = new TimeSlotService($calendar, $slot);

        $availableSpots = $timeSlotService->getAvailableSpots($startDate, $timeZone, $duration);

        if (is_wp_error($availableSpots)) {
            wp_send_json([
                'available_slots' => [],
                'timezone'        => $timeZone,
                'invalid_dates'   => true,
                'max_lookup_date' => $slot->getMaxLookUpDate(),
            ], 200);
        }

        $availableSpots = array_filter($availableSpots);
        $availableSpots = apply_filters('fluent_booking/available_slots_for_view', $availableSpots, $slot, $calendar, $timeZone, $duration);

        wp_send_json([
            'available_slots' => $availableSpots,
            'timezone'        => $timeZone,
            'max_lookup_date' => $slot->getMaxLookUpDate(),
            'execution_time'  => microtime(true) - $startBenchmark
        ], 200);
    }

    public function getCalendarEventVars(Calendar $calendar, CalendarSlot $calendarEvent)
    {
        $calendarEvent->max_lookup_date = $calendarEvent->getMaxLookUpDate();
        $calendarEvent->min_lookup_date = $calendarEvent->getMinLookUpDate();

        $calendarEvent->description = wpautop($calendarEvent->description);
        $calendarEvent->location_icon_html = $calendarEvent->defaultLocationHtml();
        $formFields = BookingFieldService::getBookingFields($calendarEvent);

        $eventData = [
            'id'                 => $calendarEvent->id,
            'max_lookup_date'    => $calendarEvent->max_lookup_date,
            'min_lookup_date'    => $calendarEvent->min_lookup_date,
            'duration'           => $calendarEvent->getDefaultDuration(),
            'title'              => $calendarEvent->title,
            'location_settings'  => $calendarEvent->location_settings,
            'location_icon_html' => $calendarEvent->location_icon_html,
            'description'        => $calendarEvent->description,
            'pre_selects'        => null,
            'settings'           => $calendarEvent->settings,
            'type'               => $calendarEvent->type,
            'time_format'        => Arr::get(get_option('_fluent_booking_settings'), 'time_format', '12'),
        ];

        $author = $calendar->getAuthorProfile(true);
        $author['name'] = $calendar->title;

        $eventVars = [
            'slot'           => $eventData,
            'author_profile' => $author,
            'form_fields'    => $formFields,
            'i18n'           => [
                'Schedule_Meeting'     => __('Schedule Meeting', 'fluent-booking-pro'),
                'Continue_to_Payments' => __('Continue to Payments', 'fluent-booking-pro'),
                'Confirm_Payment'      => __('Confirm Payment', 'fluent-booking-pro'),
            ],
            'date_formatter' => DateTimeHelper::getDateFormatter(true)
        ];

        $eventVars['form_fields'] = array_values($eventVars['form_fields']);

        if ($calendar->isTeamCalendar()) {
            $eventVars['team_member_profiles'] = $calendarEvent->getAuthorProfiles(true);
        }

        return apply_filters('fluent_booking/public_event_vars', $eventVars, $calendarEvent);
    }

    public function ajaxHandleCancelMeeting()
    {
        $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

        $meetingHash = Arr::get($data, 'meeting_hash');

        $meeting = Booking::where('hash', $meetingHash)->first();

        if (!$meeting->canCancel()) {
            wp_send_json([
                'message' => __('Sorry! you can not cancel this meeting', 'fluent-booking-pro')
            ], 422);
        }

        if (!$meeting) {
            wp_send_json([
                'message' => __('Sorry! meeting could not be found', 'fluent-booking-pro')
            ], 422);
        }

        $message = sanitize_textarea_field(Arr::get($data, 'cancellation_reason', ''));

        if (!$message) {
            wp_send_json([
                'message' => __('Please provide a reason for cancellation', 'fluent-booking-pro')
            ], 422);
        }


        $result = $meeting->cancelMeeting($message, 'guest', get_current_user_id());

        if (is_wp_error($result)) {
            if (!wp_doing_ajax()) {
                wp_redirect($meeting->getConfirmationUrl());
                exit();
            }

            wp_send_json([
                'message' => $result->get_error_message()
            ], 422);
        }

        if (wp_doing_ajax()) {
            wp_send_json([
                'message' => __('Meeting has been cancelled', 'fluent-booking-pro')
            ], 200);
        }

        wp_redirect($meeting->getConfirmationUrl());
        exit;
    }
}
