/home/edulekha/crm.edulekha.com/modules/appointly/controllers/Appointments_public.php
<?php

defined('BASEPATH') or exit('No direct script access allowed');

class Appointments_public extends ClientsController
{
    public function __construct()
    {
        parent::__construct();
        $this->load->helper('appointly');

        $this->load->model('appointly/appointly_model', 'apm');
        $this->load->model('staff_model');
    }

    /**
     * Clients hash view.
     *
     * @return void
     */
    public function client_hash()
    {
        // Try to get hash from URL segment first (for friendly URLs), then from query parameter (backward compatibility)
        $hash = $this->uri->segment(3) ?: $this->input->get('hash');
        $form = new stdClass();
        $form->language = get_option('active_language');

        $this->lang->load($form->language . '_lang', $form->language);

        if (file_exists(APPPATH . 'language/' . $form->language . '/custom_lang.php')) {
            $this->lang->load('custom_lang', $form->language);
        }

        if (!$hash) {
            show_404();
        }

        $appointment = $this->apm->get_public_appointment_by_hash($hash);

        if (!$appointment) {
            show_404();
        }
        $appointment['url'] = site_url('appointly/appointments_public/cancel_appointment');
        // Get service details
        if (isset($appointment['service_id'])) {
            $service = $this->apm->get_service($appointment['service_id']);
            if ($service) {
                $appointment['service'] = [
                    'name' => $service->name,
                    'description' => $service->description,
                    'duration' => $service->duration,
                    'price' => $service->price
                ];
            } else {
                // Service not found, set default values
                $appointment['service'] = [
                    'name' => 'Service Not Available',
                    'description' => '',
                    'duration' => 0,
                    'price' => 0
                ];
            }
        }
        $appointment['feedback_url'] = site_url('appointly/appointments_public/handleFeedbackPost');

        // Get assigned staff details
        if (isset($appointment['provider_id'])) {
            $provider = $this->staff_model->get($appointment['provider_id']);
            if ($provider) {
                $appointment['assigned_staff'] = [
                    'staffid' => $provider->staffid,
                    'full_name' => $provider->firstname . ' ' . $provider->lastname,
                    'firstname' => $provider->firstname,
                    'lastname' => $provider->lastname,
                    'email' => $provider->email,
                    'profile_image' => staff_profile_image_url($provider->staffid)
                ];
            } else {
                // Provider not found, set default values
                $appointment['assigned_staff'] = [
                    'staffid' => null,
                    'full_name' => 'Staff Member Not Available',
                    'firstname' => 'Staff Member',
                    'lastname' => 'Not Available',
                    'email' => '',
                    'profile_image' => staff_profile_image_url(0)
                ];
            }
        }

        // Check for pending reschedule requests
        $appointment['has_pending_reschedule'] = $this->apm->has_pending_reschedule($appointment['id']);

        // Get pending reschedule details if exists
        if ($appointment['has_pending_reschedule']) {
            $this->db->select('*');
            $this->db->from(db_prefix() . 'appointly_reschedule_requests');
            $this->db->where('appointment_id', $appointment['id']);
            $this->db->where('status', 'pending');
            $this->db->order_by('requested_at', 'DESC');
            $this->db->limit(1);
            $pending_reschedule = $this->db->get()->row_array();

            if ($pending_reschedule) {
                $appointment['pending_reschedule'] = $pending_reschedule;
            }
        }

        $data['appointment'] = $appointment;
        $data['form'] = $form;
        // Throws error now is ok -- we dont event use recpatcha here but we use app_external_form thats why we need this
        $data['form']->recaptcha = 0;

        $this->load->view('clients/clients_hash', $data);
    }

    /**
     * Fetches contact data if client who requested meeting is already in the system.
     *
     * @return void
     */
    public function external_fetch_contact_data()
    {
        if (! $this->input->is_ajax_request()) {
            show_404();
        }

        $id = $this->input->post('contact_id');

        header('Content-Type: application/json');
        echo json_encode($this->apm->apply_contact_data($id, false));
    }

    /**
     * Handles clients external public form.
     *
     * @return void
     */
    public function book()
    {
        $form = new stdClass();

        // Check for language parameter in URL first (user's explicit choice)
        $selected_language = $this->input->get('lang');

        // If no URL parameter, use system's active language as default
        if (!$selected_language) {
            $selected_language = get_option('active_language');
        }

        // Get all available languages from system (not just enabled ones)
        $all_languages = $this->app->get_all_languages();

        // Filter to only languages that have appointly translations
        $available_languages = [];
        foreach ($all_languages as $language) {
            if (file_exists(APPPATH . '../modules/appointly/language/' . $language . '/appointly_lang.php')) {
                $available_languages[] = $language;
            }
        }

        // If no appointly languages found, fallback to English
        if (empty($available_languages)) {
            $available_languages = ['english'];
        }

        // Validate language exists in available appointly languages
        if (!in_array($selected_language, $available_languages)) {
            $selected_language = get_option('active_language');
            // If active language doesn't have appointly translations, use English
            if (!in_array($selected_language, $available_languages)) {
                $selected_language = 'english';
            }
        }

        // Store selected language in session
        $this->session->set_userdata('appointly_external_language', $selected_language);

        $form->language = $selected_language;

        // Load appointly module language file FIRST for selected language
        if (file_exists(APPPATH . '../modules/appointly/language/' . $form->language . '/appointly_lang.php')) {
            $this->lang->load('appointly', $form->language, FALSE, TRUE, APPPATH . '../modules/appointly/');
        } else {
            // Fallback to English if selected language doesn't exist for appointly
            $this->lang->load('appointly', 'english', FALSE, TRUE, APPPATH . '../modules/appointly/');
        }

        // Then load system language file (this should not override appointly translations)
        $this->lang->load($form->language . '_lang', $form->language);

        if (file_exists(APPPATH . 'language/' . $form->language . '/custom_lang.php')) {
            $this->lang->load('custom_lang', $form->language);
        }

        // Get all active services
        $services = $this->apm->get_services();
        // Get selected services from options
        $selected_services = get_option('appointments_booking_services_availability');
        $selected_services = !empty($selected_services) ? json_decode($selected_services, true) : [];

        // Filter services if specific ones are selected
        if (!empty($selected_services)) {
            // Convert JSON strings to integers for proper comparison
            $selected_services = array_map('intval', $selected_services);
            $services = array_filter($services, static function ($service) use ($selected_services) {
                return in_array((int)$service['id'], $selected_services, true);
            });
        }

        $data = [
            'form' => $form,
            'services' => $services
        ];
        // Set recaptcha if enabled AND API keys are properly configured
        $appointly_recaptcha_enabled = get_option('appointly_appointments_recaptcha');
        $recaptcha_keys_configured = (get_option('recaptcha_secret_key') != '' && get_option('recaptcha_site_key') != '');

        // Only enable recaptcha if both settings are properly configured
        $data['form']->recaptcha = ($appointly_recaptcha_enabled == 1 && $recaptcha_keys_configured);

        // Get base currency for pricing display
        $data['baseCurrency'] = appointly_get_base_currency();

        // Check if terms and conditions are enabled
        $data['enable_terms'] = (get_option('appointments_enable_terms_conditions') == 1);
        $data['terms_link'] = get_option('terms_and_conditions_url');

        // Add available languages for language dropdown
        $data['available_languages'] = $available_languages;
        $data['current_language'] = $selected_language;
        $data['show_language_dropdown'] = (get_option('appointments_external_form_show_language_dropdown') == 1);

        $this->load->view('forms/appointments_external_form', $data);
    }

    /**
     * Handles creation of an external appointment.
     *
     * @return void
     */
    public function create_external_appointment()
    {
        $data = $this->input->post();

        // Check if reCAPTCHA is enabled and properly configured
        $appointly_recaptcha_enabled = get_option('appointly_appointments_recaptcha');
        $recaptcha_keys_configured = (get_option('recaptcha_secret_key') != '' && get_option('recaptcha_site_key') != '');
        $recaptcha_active = ($appointly_recaptcha_enabled == 1 && $recaptcha_keys_configured);

        // Skip reCAPTCHA validation if it's disabled or keys not configured
        if (!$recaptcha_active) {
            unset($data['g-recaptcha-response']);
        }
        // Validate reCAPTCHA if enabled and keys are properly configured
        else {
            if (!isset($data['g-recaptcha-response']) || !do_recaptcha_validation($data['g-recaptcha-response'])) {
                echo json_encode([
                    'success' => false,
                    'recaptcha' => false,
                    'message' => _l('recaptcha_error')
                ]);
                die;
            }
        }

        // Combine firstname and lastname into a single name field
        $data['name'] = trim($data['firstname'] . ' ' . $data['lastname']);

        // Handle logged-in client - set contact_id if client is logged in
        if (is_client_logged_in() && !empty($data['logged_in_contact_id'])) {
            // Use the contact_id directly from the form
            $data['contact_id'] = (int)$data['logged_in_contact_id'];
        }

        unset($data['firstname'], $data['lastname'], $data['logged_in_client_id'], $data['logged_in_contact_id']);
        // Process date and time fields
        if (!empty($data['date']) && !empty($data['start_hour'])) {
            // Ensure date is in correct format
            $data['date'] = to_sql_date($data['date']);

            // Format start_hour properly if needed
            if (!preg_match('/^\d{2}:\d{2}$/', $data['start_hour'])) {
                // If it's not in HH:MM format, try to convert it
                $start_time = date_create_from_format('g:i A', $data['start_hour']);
                if ($start_time) {
                    $data['start_hour'] = $start_time->format('H:i');
                }
            }

            // Calculate end_hour if not provided but we have duration
            if ((!isset($data['end_hour']) || empty($data['end_hour'])) && !empty($data['service_id'])) {
                // Get service duration
                $this->load->model('appointly/service_model');
                $service = $this->apm->get_service($data['service_id']);

                if ($service && isset($service->duration)) {
                    // Parse start hour
                    list($hours, $minutes) = explode(':', $data['start_hour']);

                    // Add duration minutes
                    $end_timestamp = mktime($hours, $minutes, 0) + ($service->duration * 60);

                    // Format end hour
                    $data['end_hour'] = date('H:i', $end_timestamp);
                }
            }
        }

        // Remove recaptcha from data before insert
        unset($data['g-recaptcha-response']);

        // Remove notification preferences - these should be managed by staff after approval
        unset($data['by_sms']);
        unset($data['by_email']);

        // Set additional fields for external appointments
        $data['rel_type'] = 'external';

        $appointment_id = $this->apm->insert_external_appointment($data);

        if ($appointment_id) {
            // Store the appointment ID in session for the success page
            $this->session->set_userdata('last_appointment_id', $appointment_id);

            // Generate a security token to prevent direct URL access to success page
            $security_token = md5('appointly_success_' . $appointment_id . time() . rand(1000, 9999));
            $this->session->set_userdata('appointly_success_token', $security_token);

            // Get current language to preserve it in success page URL
            // Use the form's submitted language or fall back to active language
            $current_language = $this->input->post('current_language') ?: get_option('active_language');
            $success_url = 'appointly/appointments_public/success_message?token=' . $security_token;
            if ($current_language && $current_language !== get_option('active_language')) {
                $success_url .= '&lang=' . $current_language;
            }

            echo json_encode([
                'success' => true,
                'message' => _l('appointment_created'),
                'appointment_id' => $appointment_id,
                'redirect_url' => site_url($success_url)
            ]);
        } else {
            echo json_encode([
                'success' => false,
                'message' => _l('appointment_creation_failed')
            ]);
        }
    }

    /**
     * Handles appointment cancelling.
     *
     * @return bool|void
     */
    public function cancel_appointment()
    {
        if ($this->input->get('hash')) {
            $hash = $this->input->get('hash');
            $notes = $this->input->get('notes');

            if ($notes == '') {
                return false;
            }

            if (! $hash) {
                show_404();
            }

            $appointment = $this->apm->get_public_appointment_by_hash($hash);

            if (! $appointment) {
                show_404();
            } else {
                $cancellation_in_progress = $this->apm->check_if_user_requested_appointment_cancellation($hash);

                header('Content-Type: application/json');
                if ($cancellation_in_progress['cancel_notes'] === null) {
                    echo json_encode($this->apm->appointment_cancellation_handler($hash, $notes));
                } else {
                    echo json_encode([
                        'response' => [
                            'message' => _l('appointments_already_applied_for_cancelling'),
                            'success' => false,
                        ],
                    ]);
                }
            }
        } else {
            show_404();
        }
    }

    public function handleFeedbackPost()
    {
        if (! $this->input->is_ajax_request()) {
            show_404();
        }

        $id = $this->input->post('id');

        if (! $id) {
            show_404();
        }

        $rating = $this->input->post('rating');

        $comment = ($this->input->post('feedback_comment')) ?: null;

        $appointmentData = $this->apm->get_appointment_data($id);

        if (!$appointmentData || !is_array($appointmentData)) {
            show_404();
            return;
        }

        // Ensure required fields exist
        $appointmentData['date'] = $appointmentData['date'] ?? date('Y-m-d');
        $appointmentData['start_hour'] = $appointmentData['start_hour'] ?? '00:00';
        $appointmentData['end_hour'] = $appointmentData['end_hour'] ?? '00:00';

        if ($this->apm->handle_feedback_post($id, $rating, $comment)) {
            echo json_encode(['success' => true]);
        } else {
            echo json_encode(['success' => false]);
        }
    }

    public function reschedule_appointment()
    {
        header('Content-Type: application/json');

        $hash = $this->input->get('hash');
        if (!$hash) {
            echo json_encode(['success' => false, 'message' => 'Missing appointment hash']);
            return;
        }

        // Get appointment by hash
        $appointment = $this->apm->get_public_appointment_by_hash($hash);
        if (!$appointment) {
            echo json_encode(['success' => false, 'message' => 'Appointment not found']);
            return;
        }

        // Check if appointment can be rescheduled
        if (!in_array($appointment['status'], ['pending', 'in-progress'])) {
            echo json_encode(['success' => false, 'message' => _l('appointment_cannot_be_rescheduled')]);
            return;
        }

        // Check if already has pending reschedule
        if ($this->apm->has_pending_reschedule($appointment['id'])) {
            echo json_encode(['success' => false, 'message' => 'A reschedule request is already pending for this appointment']);
            return;
        }

        // Get JSON input
        $input = json_decode(file_get_contents('php://input'), true);
        if (!$input) {
            echo json_encode(['success' => false, 'message' => 'Invalid request data']);
            return;
        }

        $reschedule_date = $input['reschedule_date'] ?? '';
        $reschedule_time = $input['reschedule_time'] ?? '';
        $reschedule_reason = $input['reschedule_reason'] ?? '';

        // Validation
        if (empty($reschedule_date)) {
            echo json_encode(['success' => false, 'message' => _l('appointment_reschedule_date_required')]);
            return;
        }

        if (empty($reschedule_time)) {
            echo json_encode(['success' => false, 'message' => _l('appointment_reschedule_time_required')]);
            return;
        }

        // Check if the new date/time is in the future
        $requested_datetime = $reschedule_date . ' ' . $reschedule_time;
        if (strtotime($requested_datetime) <= time()) {
            echo json_encode(['success' => false, 'message' => _l('appointment_reschedule_future_datetime')]);
            return;
        }

        // Create reschedule request in separate table
        $reschedule_id = $this->apm->create_reschedule_request(
            $appointment['id'],
            $reschedule_date,
            $reschedule_time,
            $reschedule_reason
        );

        if ($reschedule_id) {
            // Send notification to staff using shared model method
            $this->apm->_send_reschedule_notification_to_staff($appointment, $reschedule_date, $reschedule_time, $reschedule_reason);

            // Send confirmation email to client
            $template = mail_template(
                'appointly_appointment_reschedule_request_confirmation_to_client',
                'appointly',
                array_to_object($appointment)
            );

            if ($template) {
                // Set the reschedule details in merge fields
                $merge_fields = $template->get_merge_fields();
                $merge_fields['{reschedule_requested_date}'] = _dt($reschedule_date);
                $merge_fields['{reschedule_requested_time}'] = $reschedule_time;
                $merge_fields['{reschedule_reason}'] = $reschedule_reason;
                $template->set_merge_fields($merge_fields);
                $template->send();
            }

            echo json_encode([
                'success' => true,
                'message' => _l('appointment_reschedule_request_submitted')
            ]);
        } else {
            echo json_encode(['success' => false, 'message' => _l('appointment_error_occurred')]);
        }
    }



    public function get_busy_times()
    {
        $date = $this->input->get('date');
        $staff_id = $this->input->get('staff_id');
        $service_id = $this->input->get('service_id');

        if (!$date) {
            echo json_encode([]);
            die();
        }

        $busy_times = $this->apm->getBusyTimes($date, $staff_id, $service_id);
        echo json_encode($busy_times);
        die();
    }

    /**
     * Get staff schedule
     * 
     * @return json
     */
    public function get_staff_schedule()
    {
        if (!$this->input->is_ajax_request()) {
            show_404();
        }

        $staff_id = $this->input->post('staff_id');

        // Use the helper function
        $result = appointly_get_staff_schedule($staff_id);

        // Return the result as JSON
        echo json_encode($result);
    }

    public function select_request_type()
    {
        $this->load->view('forms/request_type_selection');
    }

    public function success_message()
    {
        // Verify access - Check for security token
        $token = $this->input->get('token');
        $session_token = $this->session->userdata('appointly_success_token');
        $last_appointment_id = $this->session->userdata('last_appointment_id');

        // Get the referer URL
        $referer = $this->input->server('HTTP_REFERER');
        $valid_referer = false;

        // Check if referer is from our site
        if ($referer) {
            $site_url = site_url();
            $valid_referer = (str_starts_with($referer, $site_url));
        }

        // If no token provided or token doesn't match session token or no appointment ID, redirect to booking form
        if (empty($token) || empty($session_token) || $token !== $session_token || empty($last_appointment_id) || !$valid_referer) {
            // Clear any existing session data
            $this->session->unset_userdata('last_appointment_id');
            $this->session->unset_userdata('appointly_success_token');

            // Redirect to booking form
            redirect(site_url('appointly/appointments_public/book'));
        }

        // Token is valid, clear it to prevent reuse
        $this->session->unset_userdata('appointly_success_token');

        // Check if we have an appointment ID in the session (set during booking)
        $appointment_id = $this->session->userdata('last_appointment_id');

        // If we have an appointment ID, get the appointment details
        if ($appointment_id) {
            $appointment = $this->apm->get_appointment_data($appointment_id);

            // If appointment found, add service and provider details
            if ($appointment) {
                // Add service details if available
                if (!empty($appointment['service_id'])) {
                    $service = $this->apm->get_service($appointment['service_id']);
                    if ($service) {
                        $appointment['service_name'] = $service->name;
                        $appointment['service_duration'] = $service->duration;
                        $appointment['service_description'] = $service->description;
                    }
                }

                // Add provider details if available
                if (!empty($appointment['provider_id'])) {
                    $provider = $this->staff_model->get($appointment['provider_id']);
                    if ($provider) {
                        $appointment['provider_name'] = $provider->firstname . ' ' . $provider->lastname;
                        $appointment['provider_email'] = $provider->email;
                    }
                }

                $data['appointment'] = $appointment;
            }
        }

        // Get selected language from URL parameter first (user's explicit choice)
        $selected_language = $this->input->get('lang');

        // If no URL parameter, use system's active language as default
        if (!$selected_language) {
            $selected_language = get_option('active_language');
        }

        // Get all available languages from system (not just enabled ones)
        $all_languages = $this->app->get_all_languages();

        // Filter to only languages that have appointly translations
        $available_languages = [];
        foreach ($all_languages as $language) {
            if (file_exists(APPPATH . '../modules/appointly/language/' . $language . '/appointly_lang.php')) {
                $available_languages[] = $language;
            }
        }

        // If no appointly languages found, fallback to English
        if (empty($available_languages)) {
            $available_languages = ['english'];
        }

        // Validate language exists in available appointly languages
        if (!in_array($selected_language, $available_languages)) {
            $selected_language = get_option('active_language');
            // If active language doesn't have appointly translations, use English
            if (!in_array($selected_language, $available_languages)) {
                $selected_language = 'english';
            }
        }

        $data['form'] = new stdClass();
        $data['form']->language = $selected_language;
        $data['form']->recaptcha = get_option('appointly_appointments_recaptcha');

        // Load appointly module language file FIRST for selected language
        if (file_exists(APPPATH . '../modules/appointly/language/' . $selected_language . '/appointly_lang.php')) {
            $this->lang->load('appointly', $selected_language, FALSE, TRUE, APPPATH . '../modules/appointly/');
        } else {
            // Fallback to English if selected language doesn't exist for appointly
            $this->lang->load('appointly', 'english', FALSE, TRUE, APPPATH . '../modules/appointly/');
        }

        // Then load system language file (this should not override appointly translations)
        $this->lang->load($selected_language . '_lang', $selected_language);

        if (file_exists(APPPATH . 'language/' . $selected_language . '/custom_lang.php')) {
            $this->lang->load('custom_lang', $selected_language);
        }

        // Set language-dependent data AFTER language files are loaded
        $data['message'] = _l('appointment_successfully_scheduled_message');
        $data['title'] = _l('appointment_booking_confirmed');
        $data['sub_message'] = _l('appointment_pending_approval_message');
        $data['whats_next'] = _l('appointment_whats_next');
        $data['staff_review'] = _l('appointment_staff_review');
        $data['email_confirmation'] = _l('appointment_email_confirmation');
        $data['prepare'] = _l('appointment_prepare');

        $this->load->view('forms/success_message', $data);
    }

    /**
     * AJAX endpoint for switching language on external form
     */
    public function switch_language()
    {
        if (!$this->input->is_ajax_request()) {
            show_404();
        }

        $language = $this->input->post('language');

        // Validate language exists in available languages
        $available_languages = $this->app->get_available_languages();
        if (!in_array($language, $available_languages)) {
            echo json_encode(['success' => false, 'message' => 'Invalid language']);
            return;
        }

        // Store selected language in session
        $this->session->set_userdata('appointly_external_language', $language);

        echo json_encode(['success' => true, 'redirect_url' => site_url('appointly/appointments_public/book?lang=' . $language)]);
    }

    /**
     * Generate .ics calendar file for appointment (public access)
     */
    public function download_ics($appointment_id)
    {
        if (!$appointment_id) {
            show_404();
        }

        // Use existing get_appointment_data method for consistency
        $appointment = $this->apm->get_appointment_data($appointment_id);

        if (!$appointment) {
            show_404();
        }

        // Check permissions - allow if user is staff, client contact, or public access
        $has_permission = false;

        if (is_staff_logged_in()) {
            $has_permission = true;
        } elseif (is_client_logged_in()) {
            // Check if this is the client's appointment through contact_id
            if ($appointment['source'] === 'internal' && !empty($appointment['contact_id'])) {
                $this->load->model('clients_model');
                $contact = $this->clients_model->get_contact($appointment['contact_id']);
                if ($contact && $contact->userid == get_client_user_id()) {
                    $has_permission = true;
                }
            }
        } else {
            // For public access, allow download (appointment existence is already verified)
            $has_permission = true;
        }

        if (!$has_permission) {
            show_404();
        }

        // Generate .ics content using helper function
        $ics_content = generate_appointment_ics_content($appointment);

        // Set headers for .ics download
        $filename = strtolower(_l('appointment_label')) . '_' . $appointment_id . '_' . date('Y-m-d', strtotime($appointment['date'])) . '.ics';

        header('Content-Type: text/calendar; charset=utf-8');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Cache-Control: no-cache, must-revalidate');
        header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');

        echo $ics_content;
        exit;
    }



    public function get_service_staff($service_id)
    {
        if (!$this->input->is_ajax_request()) {
            show_404();
        }

        $this->load->model('appointly/service_model');

        // Get service details
        $service = $this->service_model->get($service_id);
        if (!$service) {
            echo json_encode(['success' => false, 'message' => 'Service not found']);
            return;
        }

        // Get staff members from the service object (now populated from service_staff table)
        $staff_members = [];

        if (!empty($service->staff_members)) {
            $this->db->select('staffid, firstname, lastname, email, phonenumber');
            $this->db->from(db_prefix() . 'staff');
            $this->db->where_in('staffid', array_column($service->staff_members, 'staff_id'));
            $this->db->where('active', 1);
            $staff_query = $this->db->get();

            if ($staff_query && $staff_query->num_rows() > 0) {
                $staff_members = $staff_query->result_array();

                //  profile images
                foreach ($staff_members as &$member) {
                    $member['profile_image'] = staff_profile_image_url($member['staffid']);
                }
            }
        }

        // Get the primary provider's schedule if available, otherwise use company default
        $primary_provider_id = $service->primary_provider ?? null;

        // Format working hours - this uses the staff's working hours from settings
        $working_hours = [];
        $formatted_hours = [];

        if ($primary_provider_id) {
            // Use helper function to get staff schedule
            $schedule_data = appointly_get_staff_schedule($primary_provider_id);
            if (isset($schedule_data['data']['schedule'])) {
                $working_hours = $schedule_data['data']['schedule'];
                $formatted_hours = $schedule_data['data']['formatted_hours'];
            }
        } else {
            // Get company schedule
            $company_schedule = $this->apm->get_company_schedule();
            $company_working_hours = [];
            $company_formatted_hours = [];

            if (!empty($company_schedule)) {
                foreach ($company_schedule as $day_name => $day_data) {
                    $day_number = getWorkingDayNumber($day_name);
                    $day_key = strtolower($day_name);

                    $company_working_hours[$day_number] = [
                        'enabled' => isset($day_data['is_enabled']) && (bool) $day_data['is_enabled'],
                        'start_time' => $day_data['start_time'] ?? '09:00:00',
                        'end_time' => $day_data['end_time'] ?? '17:00:00'
                    ];

                    if (isset($day_data['is_enabled']) && $day_data['is_enabled']) {
                        $company_formatted_hours[] = [
                            'day' => _l('appointly_day_' . $day_key),
                            'day_key' => $day_key,
                            'start' => date('H:i', strtotime($day_data['start_time'] ?? '09:00:00')),
                            'end' => date('H:i', strtotime($day_data['end_time'] ?? '17:00:00'))
                        ];
                    }
                }
            }
        }

        // Prepare response data
        $response = [
            'success' => true,
            'data' => [
                'service' => [
                    'id' => $service->id,
                    'name' => $service->name,
                    'description' => $service->description,
                    'duration' => $service->duration,
                    'price' => $service->price
                ],
                'working_hours' => $working_hours,
                'formatted_hours' => $formatted_hours,
                'staff' => $staff_members
            ]
        ];

        echo json_encode($response);
        die();
    }

    public function get_services()
    {
        if (!$this->input->is_ajax_request()) {
            show_404();
        }

        $this->load->model('appointly/service_model');
        $services = $this->service_model->get_active_services();

        // Format services with necessary data
        foreach ($services as &$service) {
            $service['color'] = $service['color'] ?? '#3B82F6';  // Default to blue if no color set
        }

        echo json_encode($services);
        die();
    }

    /**
     * Get staff for a specific service
     */
    public function get_service_staff_public()
    {
        if (!$this->input->is_ajax_request()) {
            show_404();
        }

        $service_id = $this->input->post('service_id');

        // Also check GET parameters for testing/compatibility
        if (!$service_id) {
            $service_id = $this->input->get('service_id');
        }

        if (!$service_id) {
            echo json_encode([
                'success' => false,
                'message' => _l('service_id_required')
            ]);
            die();
        }

        try {
            // Get service details first
            $this->load->model('appointly/service_model');
            $service = $this->service_model->get($service_id);

            if (!$service) {
                echo json_encode([
                    'success' => false,
                    'message' => 'Service not found'
                ]);
                die();
            }

            // Get staff members for this service
            $staff_members = [];

            // Get staff IDs from service_staff table
            $this->db->select('staff_id, is_primary');
            $this->db->from(db_prefix() . 'appointly_service_staff');
            $this->db->where('service_id', $service_id);
            $service_staff = $this->db->get()->result_array();

            $staff_ids = [];
            $primary_provider_id = null;

            foreach ($service_staff as $staff) {
                $staff_ids[] = $staff['staff_id'];
                if ($staff['is_primary']) {
                    $primary_provider_id = $staff['staff_id'];
                }
            }

            // Get staff details
            if (!empty($staff_ids)) {
                $this->db->select('staffid, firstname, lastname, email, phonenumber');
                $this->db->from(db_prefix() . 'staff');
                $this->db->where_in('staffid', $staff_ids);
                $this->db->where('active', 1);
                $staff_query = $this->db->get();

                if ($staff_query && $staff_query->num_rows() > 0) {
                    $staff_members = $staff_query->result_array();

                    // Add profile images
                    foreach ($staff_members as &$member) {
                        $member['profile_image'] = staff_profile_image_url($member['staffid']);
                    }
                }
            } else {
                echo json_encode([
                    'success' => false,
                    'message' => 'No staff IDs found for service ' . $service_id
                ]);
                die();
            }

            // Get company schedule directly from database
            $this->db->select('*');
            $this->db->from(db_prefix() . 'appointly_company_schedule');
            $company_schedule_raw = $this->db->get()->result_array();

            // Create company_working_hours directly from the raw company schedule
            $company_working_hours = [];
            $company_formatted_hours = [];

            foreach ($company_schedule_raw as $day) {
                // Use weekday to get day number
                $day_name = $day['weekday'];
                $day_number = getWorkingDayNumber($day_name);
                $day_key = strtolower($day_name);

                // Build company_working_hours array
                $company_working_hours[$day_number] = [
                    'enabled' => (bool)$day['is_enabled'],
                    'start_time' => $day['start_time'],
                    'end_time' => $day['end_time']
                ];

                // Add to formatted hours if enabled
                if ($day['is_enabled']) {
                    $company_formatted_hours[] = [
                        'day' => _l('appointly_day_' . $day_key),
                        'day_key' => $day_key,
                        'start' => date('H:i', strtotime($day['start_time'])),
                        'end' => date('H:i', strtotime($day['end_time']))
                    ];
                }
            }

            // Get staff-specific schedules
            $staff_schedules = [];

            foreach ($staff_members as $staff) {
                $staff_id = $staff['staffid'];

                // Get this staff's working hours
                $hours = $this->apm->get_staff_working_hours($staff_id);
                $formatted_hours = [];
                $working_hours = [];

                if (!empty($hours)) {
                    // Staff has custom hours
                    foreach ($hours as $day => $hour_data) {
                        $day_number = getWorkingDayNumber($day);
                        $day_key = strtolower($day);

                        // First determine if this day should be enabled and what hours to use
                        $is_available = isset($hour_data['is_available']) && (bool) $hour_data['is_available'];
                        $use_company = isset($hour_data['use_company_schedule']) && (bool) $hour_data['use_company_schedule'];
                        $company_day_enabled = false;
                        $start_time = $hour_data['start_time'] ?? '09:00:00';
                        $end_time = $hour_data['end_time'] ?? '17:00:00';

                        // If using company schedule, check if that day is enabled in company schedule
                        if ($use_company) {
                            if (isset($company_working_hours[$day_number])) {
                                $company_day_enabled = isset($company_working_hours[$day_number]['enabled']) && (bool) $company_working_hours[$day_number]['enabled'];

                                if ($company_day_enabled) {
                                    $start_time = $company_working_hours[$day_number]['start_time'];
                                    $end_time = $company_working_hours[$day_number]['end_time'];

                                    // When using company schedule AND that day is enabled, 
                                    // we need to make sure is_available is true so it shows up
                                    $is_available = true;
                                }
                            }
                        }

                        // Store in the working_hours array
                        $working_hours[$day_number] = [
                            'enabled' => $use_company ? $company_day_enabled : $is_available,
                            'start_time' => $start_time,
                            'end_time' => $end_time,
                            'use_company_schedule' => $use_company
                        ];

                        // For formatted hours, include this day if:
                        // 1. Staff is available and not using company schedule, OR
                        // 2. Staff is using company schedule and that day is enabled in company schedule
                        if ((!$use_company && $is_available) || ($use_company && $company_day_enabled)) {
                            $formatted_hours[] = [
                                'day' => _l('appointly_day_' . $day_key),
                                'day_key' => $day_key,
                                'start' => date('H:i', strtotime($start_time)),
                                'end' => date('H:i', strtotime($end_time))
                            ];
                        }
                    }
                } else {
                    // Staff uses company schedule
                    $working_hours = $company_working_hours;
                    $formatted_hours = $company_formatted_hours;
                }

                $staff_schedules[$staff_id] = [
                    'working_hours' => $working_hours,
                    'formatted_hours' => $formatted_hours
                ];
            }

            // Get busy times
            $busy_times = $this->apm->get_busy_times_by_service($service_id);

            // Prepare response
            $response = [
                'success' => true,
                'data' => [
                    'service' => $service,
                    'staff' => $staff_members,
                    'staff_schedules' => $staff_schedules,
                    'working_hours' => $staff_schedules[$primary_provider_id]['working_hours'] ?? $company_working_hours,
                    'formatted_hours' => $staff_schedules[$primary_provider_id]['formatted_hours'] ?? $company_formatted_hours,
                    'busy_times' => $busy_times
                ]
            ];

            echo json_encode($response);
        } catch (Exception $e) {
            echo json_encode([
                'success' => false,
                'message' => _l('appointly_error_loading_providers')
            ]);
        }

        die();
    }

    /**
     * Get available time slots for a specific date.
     *
     * @return void
     */
    public function get_available_time_slots()
    {
        if (!$this->input->is_ajax_request()) {
            show_404();
        }

        // Get required parameters
        $service_id = $this->input->post('service_id');
        $provider_id = $this->input->post('provider_id') ?: $this->input->post('staff_id');
        $date = $this->input->post('date');
        $timezone = $this->input->post('timezone') ?: date_default_timezone_get();

        // Validate required parameters
        if (!$service_id || !$provider_id || !$date) {
            echo json_encode([
                'success' => false,
                'message' => _l('appointly_missing_required_fields')
            ]);
            die();
        }

        try {
            // Get service information
            $this->load->model('appointly/service_model');
            $service = $this->service_model->get($service_id);

            if (!$service) {
                echo json_encode([
                    'success' => false,
                    'message' => _l('appointly_service_not_found')
                ]);
                die();
            }

            // Get provider's working hours for this day
            $day_of_week = date('l', strtotime($date));

            // Initialize working hours
            $working_hours = [];
            $provider_available = false;

            try {
                // First try to get staff working hours
                $staff_hours = $this->apm->get_staff_working_hours($provider_id);
                $staff_has_custom_hours = false;

                if ($staff_hours && isset($staff_hours[$day_of_week])) {
                    $day_schedule = $staff_hours[$day_of_week];
                    $staff_has_custom_hours = true;

                    // Check if staff uses company schedule or has custom hours
                    if (isset($day_schedule['use_company_schedule']) && $day_schedule['use_company_schedule']) {
                        // Use company schedule
                        $company_schedule = $this->apm->get_company_schedule();
                        if (isset($company_schedule[$day_of_week]) && $company_schedule[$day_of_week]['is_enabled']) {
                            $provider_available = true;
                            $working_hours['start_time'] = $company_schedule[$day_of_week]['start_time'];
                            $working_hours['end_time'] = $company_schedule[$day_of_week]['end_time'];
                        }
                    } elseif (
                        isset($day_schedule['is_available'], $day_schedule['start_time']) && $day_schedule['is_available'] && isset($day_schedule['end_time'])
                    ) {
                        $provider_available = true;
                        $working_hours['start_time'] = $day_schedule['start_time'];
                        $working_hours['end_time'] = $day_schedule['end_time'];
                    }
                }
            } catch (Exception $e) {
                log_message('error', 'Error getting staff working hours: ' . $e->getMessage());
            }

            // Only fall back to company schedule if staff has NO custom hours set for this day
            if (!$provider_available && !$staff_has_custom_hours) {
                try {
                    $company_schedule = $this->apm->get_company_schedule();

                    if (isset($company_schedule[$day_of_week]) && $company_schedule[$day_of_week]['is_enabled']) {
                        $provider_available = true;
                        $working_hours['start_time'] = $company_schedule[$day_of_week]['start_time'];
                        $working_hours['end_time'] = $company_schedule[$day_of_week]['end_time'];
                    }
                } catch (Exception $e) {
                    log_message('error', 'Error getting company schedule: ' . $e->getMessage());
                }
            }

            // Check if provider is available for this day
            if (!$provider_available) {
                echo json_encode([
                    'success' => true,
                    'time_slots' => [],
                    'date' => $date,
                    'service_id' => $service_id,
                    'provider_id' => $provider_id,
                    'timezone' => $timezone,
                    'message' => _l('appointment_provider_not_available')
                ]);
                die();
            }

            // Validate that we have working hours
            if (! isset($working_hours['start_time'], $working_hours['end_time'])) {
                echo json_encode([
                    'success' => false,
                    'message' => _l('appointly_invalid_working_hours')
                ]);
                die();
            }

            // Validate working hours format
            $start_time = strtotime($working_hours['start_time']);
            $end_time = strtotime($working_hours['end_time']);

            if (!$start_time || !$end_time || $start_time >= $end_time) {
                echo json_encode([
                    'success' => false,
                    'message' => _l('appointly_invalid_working_hours')
                ]);
                die();
            }

            // Get busy times for the provider on this date
            $busy_times = $this->apm->get_busy_times_by_date($provider_id, $date);

            // Service duration in minutes
            $duration = (int) $service->duration;
            $buffer_before = (int) ($service->buffer_before ?? 0);
            $buffer_after = (int) ($service->buffer_after ?? 0);

            // Total slot time including buffers
            $slot_duration = $duration + $buffer_before + $buffer_after;

            // Generate time slots
            $slots = [];
            $current_time = $start_time;

            while ($current_time + ($slot_duration * 60) <= $end_time) {
                $slot_start = date('H:i', $current_time + ($buffer_before * 60));
                $slot_end = date('H:i', $current_time + ($buffer_before * 60) + ($duration * 60));

                // Format for display
                $display_time = $slot_start . ' - ' . $slot_end;

                // Check if slot is available (not in busy times)
                $is_available = true;

                foreach ($busy_times as $busy) {
                    $busy_start = strtotime($date . ' ' . $busy['start_hour']);
                    $busy_end = strtotime($date . ' ' . $busy['end_hour']);

                    // Convert slot times to timestamps for this date
                    $slot_start_time = strtotime($date . ' ' . $slot_start);
                    $slot_end_time = strtotime($date . ' ' . $slot_end);

                    // Check for overlap
                    if (
                        ($slot_start_time >= $busy_start && $slot_start_time < $busy_end) || // Slot start during busy
                        ($slot_end_time > $busy_start && $slot_end_time <= $busy_end) || // Slot end during busy
                        ($slot_start_time <= $busy_start && $slot_end_time >= $busy_end) // Slot contains busy
                    ) {
                        $is_available = false;
                        break;
                    }
                }

                // Add slot to array
                $slots[] = [
                    'value' => $slot_start,
                    'text' => $display_time,
                    'end_time' => $slot_end,
                    'available' => $is_available
                ];

                // Move to next slot start time
                $current_time += 30 * 60; // 30 minute intervals
            }

            // Return results
            echo json_encode([
                'success' => true,
                'time_slots' => $slots,
                'date' => $date,
                'service_id' => $service_id,
                'provider_id' => $provider_id,
                'timezone' => $timezone,
                'working_hours' => $working_hours
            ]);
        } catch (Exception $e) {
            log_message('error', 'Error in get_available_time_slots: ' . $e->getMessage());
            echo json_encode([
                'success' => false,
                'message' => _l('appointly_error_getting_time_slots'),
                'debug' => $e->getMessage()
            ]);
        }

        die();
    }

    /**
     * Get blocked days for the calendar
     *
     * @return json
     */
    public function get_blocked_days()
    {
        // Get blocked days from settings
        $blocked_days = get_option('appointly_blocked_days');
        $blocked_days_array = $blocked_days ? json_decode($blocked_days, true) : [];

        echo json_encode(['success' => true, 'blocked_days' => $blocked_days_array]);
        die();
    }
}