HEX
Server: nginx/1.24.0
System: Linux DGT-WORDPRESS-VM-SERVER 6.14.0-1014-azure #14~24.04.1-Ubuntu SMP Fri Oct 3 20:52:11 UTC 2025 x86_64
User: ubuntu (1000)
PHP: 8.4.12
Disabled: NONE
Upload Files
File: /mnt/data/dreamsai-wp/wp-content/dreams-ai/inc/functions.php
<?php
if (! defined('ABSPATH')) exit;

if (! function_exists('ds_get_booking_page_url')){
    /**
     * Resolve the booking page URL (optionally scoped to a specific service ID).
     */
    function ds_get_booking_page_url($service_id = 0){
        static $base_url = null;

        if ($base_url === null){
            $booking_page_id = function_exists('dreamsai_fl_framework_getoptions')
                ? (int) dreamsai_fl_framework_getoptions('booking_page')
                : 0;

            if ($booking_page_id){
                $base_url = get_permalink($booking_page_id);
            }

            if (! $base_url){
                $booking_page = get_page_by_path('bookings');
                if ($booking_page){
                    $base_url = get_permalink($booking_page);
                }
            }

            if (! $base_url){
                $base_url = home_url('/bookings/');
            }
        }

        $url = $base_url;
        if ($url && $service_id){
            $url = add_query_arg('service_id', (int) $service_id, $url);
        }

        return $url ?: '';
    }
}

// Ensure Staff role exists
add_action('init', function(){
    if (! get_role('staff')) {
        add_role('staff', __('Staff', 'dreams-ai'), [
            'read' => true,
        ]);
    }

function ds_resolve_service_slots_for_date($service_id, $date, $staff_id = 0, $post = null) {
    $result = [
        'slots' => array(),
        'assigned_staff_id' => 0,
    ];
    if (! $post) {
        $post = get_post($service_id);
    }
    if (! $post || $post->post_type !== 'service') {
        return $result;
    }

    if ($staff_id > 0) {
        $result['slots'] = ds_get_available_slots_for_service_date($service_id, $date, $staff_id, $post);
        $result['assigned_staff_id'] = $staff_id;
        return $result;
    }

    $service_staff_ids = (array) get_post_meta($service_id, 'service_staff_users', true);
    $service_staff_ids = array_filter(array_map('intval', $service_staff_ids));

    if (!empty($service_staff_ids)) {
        foreach ($service_staff_ids as $candidate_id) {
            $candidate_slots = ds_get_available_slots_for_service_date($service_id, $date, $candidate_id, $post);
            if (!empty($candidate_slots)) {
                $result['slots'] = $candidate_slots;
                $result['assigned_staff_id'] = $candidate_id;
                return $result;
            }
        }
    }

    $result['slots'] = ds_get_available_slots_for_service_date($service_id, $date, 0, $post);
    return $result;
}

    // Service Amenities (flat, non-hierarchical)
    if (! taxonomy_exists('service_amenities')){
        register_taxonomy('service_amenities', ['service','product'], [
            'labels' => [
                'name'              => __('Amenities', 'dreams-ai'),
                'singular_name'     => __('Amenity', 'dreams-ai'),
                'search_items'      => __('Search Amenities', 'dreams-ai'),
                'all_items'         => __('All Amenities', 'dreams-ai'),
                'edit_item'         => __('Edit Amenity', 'dreams-ai'),
                'update_item'       => __('Update Amenity', 'dreams-ai'),
                'add_new_item'      => __('Add New Amenity', 'dreams-ai'),
                'new_item_name'     => __('New Amenity Name', 'dreams-ai'),
                'menu_name'         => __('Amenities', 'dreams-ai'),
            ],
            'hierarchical'      => false,
            'public'            => true,
            'show_ui'           => true,
            'show_admin_column' => true,
            'show_in_rest'      => true,
            'rewrite'           => [ 'slug' => 'amenity', 'with_front' => false ],
        ]);
    }
    // Why Book With Us (flat)
    if (! taxonomy_exists('service_why_book')){
        register_taxonomy('service_why_book', ['service','product'], [
            'labels' => [
                'name'              => __('Why Book With Us', 'dreams-ai'),
                'singular_name'     => __('Why Book Item', 'dreams-ai'),
                'search_items'      => __('Search Why Book Items', 'dreams-ai'),
                'all_items'         => __('All Why Book Items', 'dreams-ai'),
                'edit_item'         => __('Edit Why Book Item', 'dreams-ai'),
                'update_item'       => __('Update Why Book Item', 'dreams-ai'),
                'add_new_item'      => __('Add New Why Book Item', 'dreams-ai'),
                'new_item_name'     => __('New Why Book Item Name', 'dreams-ai'),
                'menu_name'         => __('Why Book With Us', 'dreams-ai'),
            ],
            'hierarchical'      => false,
            'public'            => true,
            'show_ui'           => true,
            'show_admin_column' => true,
            'show_in_rest'      => true,
            'rewrite'           => [ 'slug' => 'why-book', 'with_front' => false ],
        ]);
    }
});

// Register reusable taxonomies for Services (and Products)
add_action('init', function(){
    // Service Categories (hierarchical)
    if (! taxonomy_exists('service_category')){
        register_taxonomy('service_category', ['service','product'], [
            'labels' => [
                'name'              => __('Service Categories', 'dreams-ai'),
                'singular_name'     => __('Service Category', 'dreams-ai'),
                'search_items'      => __('Search Service Categories', 'dreams-ai'),
                'all_items'         => __('All Service Categories', 'dreams-ai'),
                'parent_item'       => __('Parent Service Category', 'dreams-ai'),
                'parent_item_colon' => __('Parent Service Category:', 'dreams-ai'),
                'edit_item'         => __('Edit Service Category', 'dreams-ai'),
                'update_item'       => __('Update Service Category', 'dreams-ai'),
                'add_new_item'      => __('Add New Service Category', 'dreams-ai'),
                'new_item_name'     => __('New Service Category Name', 'dreams-ai'),
                'menu_name'         => __('Service Categories', 'dreams-ai'),
            ],
            'hierarchical'      => true,
            'public'            => true,
            'show_ui'           => true,
            'show_admin_column' => true,
            'show_in_rest'      => true,
            'rewrite'           => [ 'slug' => 'service-category', 'with_front' => false ],
        ]);
    }

    // Service Branches (locations) - hierarchical to allow region > branch
    if (! taxonomy_exists('service_branch')){
        register_taxonomy('service_branch', ['service','product'], [
            'labels' => [
                'name'              => __('Service Branches', 'dreams-ai'),
                'singular_name'     => __('Service Branch', 'dreams-ai'),
                'search_items'      => __('Search Service Branches', 'dreams-ai'),
                'all_items'         => __('All Service Branches', 'dreams-ai'),
                'parent_item'       => __('Parent Branch', 'dreams-ai'),
                'parent_item_colon' => __('Parent Branch:', 'dreams-ai'),
                'edit_item'         => __('Edit Branch', 'dreams-ai'),
                'update_item'       => __('Update Branch', 'dreams-ai'),
                'add_new_item'      => __('Add New Branch', 'dreams-ai'),
                'new_item_name'     => __('New Branch Name', 'dreams-ai'),
                'menu_name'         => __('Service Branches', 'dreams-ai'),
            ],
            'hierarchical'      => true,
            'public'            => true,
            'show_ui'           => true,
            'show_admin_column' => true,
            'show_in_rest'      => true,
            'rewrite'           => [ 'slug' => 'service-branch', 'with_front' => false ],
        ]);
    }
});

// Enqueue admin scripts/styles for service edit screen


// AJAX: Load staff filtered by categories/branches
add_action('wp_ajax_ds_load_staff', function(){
    if (!isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    if (!is_user_logged_in()){
        wp_send_json_error(['message' => 'Unauthorized'], 401);
    }
    $cats = isset($_POST['categories']) ? (array) $_POST['categories'] : array();
    $brs  = isset($_POST['branches']) ? (array) $_POST['branches'] : array();
    $cats = array_filter(array_map('intval', $cats));
    $brs  = array_filter(array_map('intval', $brs));

    $args = array(
        'role'    => 'staff',
        'number'  => 200,
        'orderby' => 'display_name',
        'order'   => 'ASC',
        'fields'  => array('ID','display_name'),
    );
    $users = get_users($args);

    $out = array();
    foreach ($users as $u){
        // optional status filter
        $status = get_user_meta($u->ID, 'staff_status', true);
        if ($status && $status !== 'active') continue;

        $ucats = (array) get_user_meta($u->ID, 'staff_categories', true);
        $ubrs  = (array) get_user_meta($u->ID, 'staff_branches', true);
        $ucats = array_filter(array_map('intval', $ucats));
        $ubrs  = array_filter(array_map('intval', $ubrs));

        $ok = true;
        if (!empty($cats)){
            $ok = (count(array_intersect($cats, $ucats)) > 0);
        }
        if ($ok && !empty($brs)){
            $ok = (count(array_intersect($brs, $ubrs)) > 0);
        }
        if ($ok){
            $out[] = array('ID' => (int) $u->ID, 'name' => $u->display_name ?: ('User #'.$u->ID));
        }
    }

    wp_send_json_success(array('users' => $out));
});

// Register meta boxes for Service post type
add_action('add_meta_boxes', function(){
    add_meta_box(
        'ds_service_scheduling',
        __('Service Scheduling', 'dreams-ai'),
        'ds_render_service_scheduling_metabox',
        'service',
        'normal',
        'high'
    );
    add_meta_box(
        'ds_service_assignment',
        __('Branch & Staff Assignment', 'dreams-ai'),
        'ds_render_service_assignment_metabox',
        'service',
        'normal',
        'default'
    );
});

function ds_render_service_scheduling_metabox($post){
    wp_nonce_field('ds_save_service_meta', 'ds_service_meta_nonce');
    $start_time   = get_post_meta($post->ID, 'service_start_time', true);
    $end_time     = get_post_meta($post->ID, 'service_end_time', true);
    $interval     = (int) get_post_meta($post->ID, 'service_slot_interval', true) ?: 30;
    $dur_hours    = (int) get_post_meta($post->ID, 'service_duration_hours', true);
    $dur_minutes  = (int) get_post_meta($post->ID, 'service_duration_minutes', true);
    $slots_times  = (array) get_post_meta($post->ID, 'service_slots_times', true);
    ?>
    <div class="ds-field-row">
        <label for="ds_start_time" class="ds-label"><?php esc_html_e('Start Time', 'dreams-ai'); ?></label>
        <input type="time" id="ds_start_time" name="ds_start_time" value="<?php echo esc_attr($start_time); ?>" />
        <label for="ds_end_time" class="ds-label ms-3"><?php esc_html_e('End Time', 'dreams-ai'); ?></label>
        <input type="time" id="ds_end_time" name="ds_end_time" value="<?php echo esc_attr($end_time); ?>" />
        <label for="ds_interval" class="ds-label ms-3"><?php esc_html_e('Interval', 'dreams-ai'); ?></label>
        <select id="ds_interval" name="ds_interval">
            <option value="30" <?php selected($interval, 30); ?>><?php esc_html_e('30 minutes', 'dreams-ai'); ?></option>
            <option value="60" <?php selected($interval, 60); ?>><?php esc_html_e('60 minutes', 'dreams-ai'); ?></option>
        </select>
        <button type="button" class="button button-primary ms-2" id="ds_generate_slots_btn"><?php esc_html_e('Generate Slots', 'dreams-ai'); ?></button>
    </div>
    <div class="ds-field-row mt-2">
        <label class="ds-label"><?php esc_html_e('Duration Hours', 'dreams-ai'); ?></label>
        <select name="ds_duration_hours" id="ds_duration_hours">
            <option value="">—</option>
            <?php for ($h=1;$h<=12;$h++): ?>
                <option value="<?php echo (int)$h; ?>" <?php selected($dur_hours, $h); ?>><?php echo (int)$h; ?></option>
            <?php endfor; ?>
        </select>
        <label class="ds-label ms-3"><?php esc_html_e('Duration Minutes', 'dreams-ai'); ?></label>
        <select name="ds_duration_minutes" id="ds_duration_minutes">
            <option value="">—</option>
            <?php for ($m=1;$m<=59;$m++): ?>
                <option value="<?php echo (int)$m; ?>" <?php selected($dur_minutes, $m); ?>><?php echo (int)$m; ?></option>
            <?php endfor; ?>
        </select>
    </div>
    <div class="ds-field-row mt-3">
        <strong><?php esc_html_e('Generated Slots', 'dreams-ai'); ?></strong>
        <div id="ds_generated_slots" class="ds-slots-wrap">
            <?php if (!empty($slots_times) && is_array($slots_times)):
                foreach ($slots_times as $slot): ?>
                    <span class="ds-slot-chip"><?php echo esc_html($slot); ?></span>
                <?php endforeach; else: ?>
                <em><?php esc_html_e('No slots generated yet.', 'dreams-ai'); ?></em>
            <?php endif; ?>
        </div>
        <div id="ds_generated_slots_inputs">
            <?php if (!empty($slots_times) && is_array($slots_times)):
                foreach ($slots_times as $slot): ?>
                    <input type="hidden" name="ds_generated_slots[]" value="<?php echo esc_attr($slot); ?>" />
                <?php endforeach; endif; ?>
        </div>
    </div>
    <?php
}

function ds_render_service_assignment_metabox($post){
    $selected_branches = [];
    $selected_staff    = (array) get_post_meta($post->ID, 'service_staff_users', true);
    // Branch field: prefer taxonomy `service_branch`, else CPT `branch`
    $use_tax = taxonomy_exists('service_branch');
    if ($use_tax) {
        $selected_branches = wp_get_object_terms($post->ID, 'service_branch', ['fields' => 'ids']);
        $terms = get_terms(['taxonomy' => 'service_branch', 'hide_empty' => false]);
        echo '<p><label class="ds-label">' . esc_html__('Branch Locations', 'dreams-ai') . '</label><br/>';
        echo '<select multiple name="ds_branch_terms[]" id="ds_branch_terms" style="min-width:280px;">';
        if (!is_wp_error($terms)){
            foreach ($terms as $t){
                printf('<option value="%d" %s>%s</option>', (int)$t->term_id, selected(in_array($t->term_id, (array)$selected_branches, true), true, false), esc_html($t->name));
            }
        }
        echo '</select></p>';
    } else {
        // Fallback to CPT `branch`
        $selected_branches = (array) get_post_meta($post->ID, 'service_branches', true);
        $branches = get_posts(['post_type'=>'branch','numberposts'=>-1,'post_status'=>'publish']);
        echo '<p><label class="ds-label">' . esc_html__('Branch Locations', 'dreams-ai') . '</label><br/>';
        echo '<select multiple name="ds_branch_posts[]" id="ds_branch_posts" style="min-width:280px;">';
        foreach ($branches as $b){
            printf('<option value="%d" %s>%s</option>', (int)$b->ID, selected(in_array($b->ID, (array)$selected_branches, true), true, false), esc_html($b->post_title));
        }
        echo '</select></p>';
    }

    // Staff multi-select
    echo '<p><label class="ds-label">' . esc_html__('Assign Staff', 'dreams-ai') . '</label><br/>';
    echo '<select multiple name="ds_staff_users[]" id="ds_staff_users" style="min-width:280px;">';
    // Prefill saved selections
    if (!empty($selected_staff)){
        $users = get_users(['include' => array_map('intval', $selected_staff)]);
        foreach ($users as $u){
            printf('<option value="%d" selected>%s</option>', (int)$u->ID, esc_html($u->display_name));
        }
    }
    echo '</select></p>';

    // Helper for dynamic filtering via AJAX
    $category_terms = wp_get_object_terms($post->ID, 'service_category', ['fields' => 'ids']);
    ?>
    <script>
    jQuery(function($){
        function loadStaff(){
            var data = {
                action: 'ds_load_staff',
                _ajax_nonce: DSAjax.nonce,
                categories: <?php echo wp_json_encode(array_map('intval', (array)$category_terms)); ?>,
                branches: $('#ds_branch_terms').length ? $('#ds_branch_terms').val() : $('#ds_branch_posts').val()
            };
            $.post(DSAjax.ajaxUrl, data, function(resp){
                if (!resp || !resp.success) return;
                var $sel = $('#ds_staff_users');
                var preselected = (<?php echo wp_json_encode(array_map('intval', (array)$selected_staff)); ?>) || [];
                $sel.empty();
                $.each(resp.data.users || [], function(_, u){
                    var selected = preselected.includes(parseInt(u.ID));
                    var opt = $('<option>').val(u.ID).text(u.name);
                    if (selected) opt.attr('selected','selected');
                    $sel.append(opt);
                });
            });
        }
        $('#ds_branch_terms, #ds_branch_posts').on('change', loadStaff);
        // Initial load
        loadStaff();
    });
    </script>
    <?php
}

// Save meta box data
add_action('save_post_service', function($post_id, $post, $update){
    if (! isset($_POST['ds_service_meta_nonce']) || ! wp_verify_nonce($_POST['ds_service_meta_nonce'], 'ds_save_service_meta')) return;
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
    if (! current_user_can('edit_post', $post_id)) return;

    // Times & interval
    $start = isset($_POST['ds_start_time']) ? sanitize_text_field($_POST['ds_start_time']) : '';
    $end   = isset($_POST['ds_end_time']) ? sanitize_text_field($_POST['ds_end_time']) : '';
    $interval = isset($_POST['ds_interval']) ? (int) $_POST['ds_interval'] : 30;
    update_post_meta($post_id, 'service_start_time', $start);
    update_post_meta($post_id, 'service_end_time', $end);
    update_post_meta($post_id, 'service_slot_interval', $interval);

    // Duration
    $dh = isset($_POST['ds_duration_hours']) ? (int) $_POST['ds_duration_hours'] : 0;
    $dm = isset($_POST['ds_duration_minutes']) ? (int) $_POST['ds_duration_minutes'] : 0;
    update_post_meta($post_id, 'service_duration_hours', max(0, $dh));
    update_post_meta($post_id, 'service_duration_minutes', max(0, min(59, $dm)));

    // Generated slots
    $slots = isset($_POST['ds_generated_slots']) ? array_map('sanitize_text_field', (array) $_POST['ds_generated_slots']) : [];
    update_post_meta($post_id, 'service_slots_times', $slots);

    // Branch save
    if (taxonomy_exists('service_branch') && isset($_POST['ds_branch_terms'])){
        $terms = array_map('intval', (array) $_POST['ds_branch_terms']);
        wp_set_object_terms($post_id, $terms, 'service_branch');
    } elseif (isset($_POST['ds_branch_posts'])){
        $posts = array_map('intval', (array) $_POST['ds_branch_posts']);
        update_post_meta($post_id, 'service_branches', $posts);
    }

    // Staff users
    if (isset($_POST['ds_staff_users'])){
        $staff = array_map('intval', (array) $_POST['ds_staff_users']);
        update_post_meta($post_id, 'service_staff_users', $staff);
    }
}, 10, 3);

// AJAX: generate slots between start and end at interval
add_action('wp_ajax_ds_generate_slots', function(){
    check_ajax_referer('ds_admin_nonce');
    $start = isset($_POST['start']) ? sanitize_text_field($_POST['start']) : '';
    $end   = isset($_POST['end']) ? sanitize_text_field($_POST['end']) : '';
    $interval = isset($_POST['interval']) ? (int) $_POST['interval'] : 30;
    $slots = ds_build_time_slots($start, $end, $interval);
    wp_send_json_success(['slots' => $slots]);
});

function ds_build_time_slots($start, $end, $interval){
    $out = [];
    if (!$start || !$end) return $out;
    // Expect format HH:MM
    $s_parts = explode(':', $start);
    $e_parts = explode(':', $end);
    if (count($s_parts) < 2 || count($e_parts) < 2) return $out;
    $s = ((int)$s_parts[0]) * 60 + (int)$s_parts[1];
    $e = ((int)$e_parts[0]) * 60 + (int)$e_parts[1];
    if ($e <= $s || $interval <= 0) return $out;
    for ($m = $s; $m + $interval <= $e; $m += $interval){
        $h = floor($m / 60); $min = $m % 60;
        $h2 = floor(($m + $interval) / 60); $min2 = ($m + $interval) % 60;
        $out[] = sprintf('%02d:%02d-%02d:%02d', $h, $min, $h2, $min2);
    }
    return $out;
}

// AJAX: load staff based on selected categories and branches
add_action('wp_ajax_ds_load_staff', function(){
    check_ajax_referer('ds_admin_nonce');
    $category_ids = isset($_POST['categories']) ? array_map('intval', (array) $_POST['categories']) : [];
    $branch_ids   = isset($_POST['branches']) ? array_map('intval', (array) $_POST['branches']) : [];

    // Basic strategy: users with role 'staff' and user meta arrays containing intersection
    $args = [
        'role__in' => ['staff','editor','author','administrator'], // allow customization
        'number'   => 200,
        'fields'   => ['ID','display_name'],
        'meta_query' => [ 'relation' => 'AND' ]
    ];
    // Filter by branches if provided (meta: staff_branches as array of IDs)
    if (!empty($branch_ids)){
        $args['meta_query'][] = [
            'key'     => 'staff_branches',
            'value'   => array_map('strval', $branch_ids),
            'compare' => 'IN'
        ];
    }
    // Filter by categories if provided (meta: staff_categories)
    if (!empty($category_ids)){
        $args['meta_query'][] = [
            'key'     => 'staff_categories',
            'value'   => array_map('strval', $category_ids),
            'compare' => 'IN'
        ];
    }
    $users = get_users($args);
    $out = [];
    foreach ($users as $u){
        $out[] = ['ID' => $u->ID, 'name' => $u->display_name];
    }
    wp_send_json_success(['users' => $out]);
});

// AJAX: load branches based on selected categories
// ===== Frontend Booking: Branch -> Categories =====
add_action('wp_ajax_ds_get_branch_categories', 'ds_ajax_get_branch_categories');
add_action('wp_ajax_nopriv_ds_get_branch_categories', 'ds_ajax_get_branch_categories');
function ds_ajax_get_branch_categories(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    $branch_id = isset($_POST['branch_id']) ? (int) $_POST['branch_id'] : 0;

    $service_ids = [];
    if ($branch_id > 0){
        $q = new WP_Query([
            'post_type'      => 'service',
            'post_status'    => 'publish',
            'posts_per_page' => 300,
            'fields'         => 'ids',
            'tax_query'      => [[
                'taxonomy' => 'service_branch',
                'field'    => 'term_id',
                'terms'    => [$branch_id],
            ]],
        ]);
        $service_ids = $q->posts;
    }

    if (empty($service_ids)){
        wp_send_json_success(['categories' => []]);
    }

    $term_query = new WP_Term_Query([
        'taxonomy'   => 'service_category',
        'hide_empty' => false,
        'object_ids' => $service_ids,
        'orderby'    => 'name',
        'order'      => 'ASC',
    ]);
    $cats_out = [];
    if (! is_wp_error($term_query) && ! empty($term_query->terms)){
        foreach ($term_query->terms as $t){
            $cnt_q = new WP_Query([
                'post_type'      => 'service',
                'post_status'    => 'publish',
                'posts_per_page' => 1,
                'fields'         => 'ids',
                'tax_query'      => [
                    'relation' => 'AND',
                    [
                        'taxonomy' => 'service_branch',
                        'field'    => 'term_id',
                        'terms'    => [$branch_id],
                    ],
                    [
                        'taxonomy' => 'service_category',
                        'field'    => 'term_id',
                        'terms'    => [(int) $t->term_id],
                    ],
                ],
            ]);
            $nm = $t->name;
            if (function_exists('mb_convert_case')) {
                $nm = mb_convert_case($nm, MB_CASE_TITLE, 'UTF-8');
            } else {
                $nm = ucwords(strtolower($nm));
            }
            $cats_out[] = [
                'id'    => (int) $t->term_id,
                'name'  => $nm,
                'count' => (int) $cnt_q->found_posts,
            ];
        }
    }

    wp_send_json_success(['categories' => $cats_out]);
}

// ===== Frontend Booking: Load Services grid =====
add_action('wp_ajax_ds_get_services', 'ds_ajax_get_services');
add_action('wp_ajax_nopriv_ds_get_services', 'ds_ajax_get_services');
function ds_ajax_get_services(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    $branch_id   = isset($_POST['branch_id']) ? (int) $_POST['branch_id'] : 0;
    $category_id = isset($_POST['category_id']) ? (int) $_POST['category_id'] : 0;

    $tax_query = [];
    if ($branch_id > 0){
        $tax_query[] = [
            'taxonomy' => 'service_branch',
            'field'    => 'term_id',
            'terms'    => [$branch_id],
        ];
    }
    if ($category_id > 0){
        $tax_query[] = [
            'taxonomy' => 'service_category',
            'field'    => 'term_id',
            'terms'    => [$category_id],
        ];
    }
    if (count($tax_query) > 1){ $tax_query['relation'] = 'AND'; }

    $q = new WP_Query([
        'post_type'      => 'service',
        'post_status'    => 'publish',
        'posts_per_page' => 24,
        'orderby'        => 'date',
        'order'          => 'DESC',
        'tax_query'      => $tax_query,
    ]);

    ob_start();
    echo '<div class="d-flex flex-wrap gap-3 justify-content-start">';
    if ($q->have_posts()){
        while ($q->have_posts()){ $q->the_post();
            $sid   = get_the_ID();
            $title = get_the_title();
            if ($title !== ''){
                if (function_exists('mb_convert_case')) { $title = mb_convert_case($title, MB_CASE_TITLE, 'UTF-8'); }
                else { $title = ucwords(strtolower($title)); }
            }
            $price = get_post_meta($sid, 'service_price', true);
            $offer = get_post_meta($sid, 'service_offer_price', true);
            $dur_h = (int) get_post_meta($sid, 'service_duration_hours', true);
            $dur_m = (int) get_post_meta($sid, 'service_duration_minutes', true);
            // Branch name (first service_branch term)
            $branch_name = '';
            $branch_terms = function_exists('get_the_terms') ? get_the_terms($sid, 'service_branch') : [];
            if ($branch_terms && ! is_wp_error($branch_terms)) {
                $branch_name = $branch_terms[0]->name;
            }
            // Reviews: compute average rating and count from comment meta 'rating'
            $avg_rating = 0;
            $rating_count = 0;
            $comments = get_approved_comments($sid);
            if (! empty($comments)) {
                $sum = 0; $n = 0;
                foreach ($comments as $c) {
                    $r = get_comment_meta($c->comment_ID, 'rating', true);
                    if ($r !== '' && $r !== null) {
                        $sum += floatval($r);
                        $n++;
                    }
                }
                if ($n > 0) {
                    $avg_rating = $sum / $n;
                    $rating_count = $n;
                }
            }

            $thumb = '';
            $gallery_ids = get_post_meta($sid, 'service_gallery', true);
            if (is_array($gallery_ids) && !empty($gallery_ids)){
                $first_id = (int) reset($gallery_ids);
                $img = wp_get_attachment_image_url($first_id, 'medium');
                if ($img) { $thumb = $img; }
            }
            if (!$thumb){
                $fi = get_the_post_thumbnail_url($sid, 'medium');
                if ($fi) { $thumb = $fi; }
            }
            if (! $thumb){ $thumb = 'https://via.placeholder.com/300x200?text=Service'; }

            $dur_parts = [];
            if ($dur_h > 0){ $dur_parts[] = $dur_h . ' Hrs'; }
            if ($dur_m > 0){ $dur_parts[] = $dur_m . ' Mins'; }
            $dur_text = !empty($dur_parts) ? implode(' ', $dur_parts) : '';

            echo '<div class="card">';
            echo '  <div class="card-body service-card p-3">';
            echo '    <img class="rounded-3 overflow-hidden ds-service-trigger" role="button" tabindex="0" data-service-id="' . (int)$sid . '" src="' . esc_url($thumb) . '" style="width: 100%; height: 150px; object-fit: cover; cursor: pointer;" alt="' . esc_attr($title) . '" />';
            echo '    <div class="mt-3">';
            echo '      <h5 class="fw-semibold mb-1 ds-service-trigger" role="button" tabindex="0" data-service-id="' . (int)$sid . '">' . esc_html($title) . '</h5>';
            if ($branch_name !== ''){
                echo '  <p class="text-muted small mb-1"> <i class="ti ti-map-pin me-1"></i>' . esc_html($branch_name) . '</p>';
            }
            if ($dur_text){ echo '  <p class="text-muted small mb-2"><i class="ti ti-clock me-1"></i>' . esc_html($dur_text) . '</p>'; }
            // Reviews row
                echo '  <div class="d-flex align-items-center mb-2 ratingsstar">';
                $stars = round($avg_rating);
                for ($i = 1; $i <= 5; $i++){
                    $cls = ($i <= $stars) ? 'text-warning' : 'text-muted';
                    echo '    <i class="ti ti-star-filled ' . esc_attr($cls) . '"></i>';
                }
                echo '    <span class="small ms-1">' . esc_html(number_format_i18n($avg_rating, 1)) . ' (' . esc_html(number_format_i18n($rating_count)) . ')</span>';
                echo '  </div>';
            echo '      <div class="d-flex justify-content-between align-items-center">';
            // Prices: show offer price (highlighted) with base price crossed-out when applicable
            $base_amount = ($price !== '' ? (float)$price : 0);
            $offer_amount = ($offer !== '' ? (float)$offer : 0);
            if ($offer_amount > 0 || $base_amount > 0){
                $sym = function_exists('get_woocommerce_currency_symbol') ? get_woocommerce_currency_symbol() : '$';
                if ($offer_amount > 0){
                    if (function_exists('wc_price')){
                        $offer_formatted = wp_kses_post(wc_price($offer_amount));
                        $base_formatted  = ($base_amount > 0 ? wp_kses_post(wc_price($base_amount)) : '');
                    } else {
                        $offer_formatted = esc_html($sym . number_format_i18n($offer_amount, 2));
                        $base_formatted  = ($base_amount > 0 ? esc_html($sym . number_format_i18n($base_amount, 2)) : '');
                    }
                    echo '    <span class="price text-danger fw-bold">' . $offer_formatted . '</span>';
                    if ($base_amount > 0 && $base_amount !== $offer_amount){
                        echo '    <span class="text-muted text-decoration-line-through small ms-1">' . $base_formatted . '</span>';
                    }
                } else {
                    if (function_exists('wc_price')){
                        $base_formatted = wp_kses_post(wc_price($base_amount));
                    } else {
                        $base_formatted = esc_html($sym . number_format_i18n($base_amount, 2));
                    }
                    echo '    <span class="price fw-bold">' . $base_formatted . '</span>';
                }
            } else {
                echo '    <span class="price">&nbsp;</span>';
            }
            echo '        <button type="button" class="btn btn-sm btn-dark ds-add-to-book" data-service-id="' . (int)$sid . '"><i class="ti ti-plus"></i></button>';
            echo '      </div>';
            echo '    </div>';
            echo '  </div>';
            echo '</div>';
        }
        wp_reset_postdata();
    } else {
        echo '<div class="col-12"><em>' . esc_html__('No services found for this category.', 'dreams-ai') . '</em></div>';
    }
    echo '</div>';
    $html = ob_get_clean();

    wp_send_json_success(['html' => $html]);
}

// ===== Single Service: Submit Review =====
add_action('wp_ajax_ds_submit_review', 'ds_ajax_submit_review');
add_action('wp_ajax_nopriv_ds_submit_review', 'ds_ajax_submit_review');
function ds_ajax_submit_review(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }

    $post_id = isset($_POST['post_id']) ? (int) $_POST['post_id'] : 0;
    $rating  = isset($_POST['rating']) ? (int) $_POST['rating'] : 0;
    $content = isset($_POST['content']) ? sanitize_textarea_field(wp_unslash($_POST['content'])) : '';

    if (! $post_id || $content === '' || $rating < 1 || $rating > 5){
        wp_send_json_error(['message' => __('Please provide rating and review content.', 'dreams-ai')], 400);
    }

    $post = get_post($post_id);
    if (! $post || $post->post_type !== 'service'){
        wp_send_json_error(['message' => __('Invalid service.', 'dreams-ai')], 404);
    }

    if (is_user_logged_in()){
        $user    = wp_get_current_user();
        $author  = $user->display_name ?: $user->user_login;
        $email   = $user->user_email;
        $user_id = (int) $user->ID;
    } else {
        $author  = isset($_POST['author']) ? sanitize_text_field($_POST['author']) : '';
        $email   = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
        $user_id = 0;
    }
    // Ensure comments are open
    if ('open' !== get_post_field('comment_status', $post_id)){
        wp_update_post(['ID' => $post_id, 'comment_status' => 'open']);
    }
    $commentdata = array(
        'comment_post_ID'      => $post_id,
        'comment_content'      => $content,
        'comment_author'       => $author,
        'comment_author_email' => $email,
        'user_id'              => $user_id,
        'comment_approved'     => 1,
    );
    $comment_id = wp_insert_comment($commentdata);
    if (! $comment_id || is_wp_error($comment_id)){
        wp_send_json_error(['message' => __('Failed to submit review. Please try again.', 'dreams-ai')], 500);
    }
    if ($rating > 0){
        add_comment_meta($comment_id, 'rating', $rating, true);
    }
    wp_send_json_success(['message' => __('Review submitted successfully.', 'dreams-ai')]);
}

// ===== Reviews: Update, Delete, and Reply (AJAX) =====
add_action('wp_ajax_ds_update_review', 'ds_ajax_update_review');
function ds_ajax_update_review(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    if (! is_user_logged_in()){
        wp_send_json_error(['message' => 'Unauthorized'], 401);
    }
    $comment_id = isset($_POST['comment_id']) ? (int) $_POST['comment_id'] : 0;
    $content    = isset($_POST['content']) ? sanitize_textarea_field(wp_unslash($_POST['content'])) : '';
    $rating     = isset($_POST['rating']) ? (int) $_POST['rating'] : 0;
    if (! $comment_id || $content === ''){
        wp_send_json_error(['message' => __('Missing fields.', 'dreams-ai')], 400);
    }
    $comment = get_comment($comment_id);
    if (! $comment){
        wp_send_json_error(['message' => __('Comment not found.', 'dreams-ai')], 404);
    }
    $user = wp_get_current_user();
    $can_edit = ((int)$comment->user_id === (int)$user->ID) || current_user_can('edit_comment', $comment_id) || current_user_can('moderate_comments');
    if (! $can_edit){
        wp_send_json_error(['message' => __('You do not have permission to edit this review.', 'dreams-ai')], 403);
    }
    $ok = wp_update_comment([
        'comment_ID'      => $comment_id,
        'comment_content' => $content,
    ]);
    if (! $ok){
        wp_send_json_error(['message' => __('Failed to update review.', 'dreams-ai')], 500);
    }
    if ($rating >= 1 && $rating <= 5){
        update_comment_meta($comment_id, 'rating', $rating);
    }
    wp_send_json_success(['message' => __('Review updated.', 'dreams-ai')]);
}

add_action('wp_ajax_ds_delete_review', 'ds_ajax_delete_review');
function ds_ajax_delete_review(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    if (! is_user_logged_in()){
        wp_send_json_error(['message' => 'Unauthorized'], 401);
    }
    $comment_id = isset($_POST['comment_id']) ? (int) $_POST['comment_id'] : 0;
    if (! $comment_id){
        wp_send_json_error(['message' => __('Missing comment.', 'dreams-ai')], 400);
    }
    $comment = get_comment($comment_id);
    if (! $comment){
        wp_send_json_error(['message' => __('Comment not found.', 'dreams-ai')], 404);
    }
    $user = wp_get_current_user();
    $can_delete = ((int)$comment->user_id === (int)$user->ID) || current_user_can('edit_comment', $comment_id) || current_user_can('moderate_comments');
    if (! $can_delete){
        wp_send_json_error(['message' => __('You do not have permission to delete this review.', 'dreams-ai')], 403);
    }
    $deleted = wp_trash_comment($comment_id);
    if (is_wp_error($deleted) || ! $deleted){
        $deleted = wp_delete_comment($comment_id, true);
    }
    if (! $deleted){
        wp_send_json_error(['message' => __('Failed to delete review.', 'dreams-ai')], 500);
    }
    wp_send_json_success(['message' => __('Review deleted.', 'dreams-ai')]);
}

add_action('wp_ajax_ds_reply_review', 'ds_ajax_reply_review');
function ds_ajax_reply_review(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    if (! is_user_logged_in()){
        wp_send_json_error(['message' => 'Unauthorized'], 401);
    }
    $parent_id = isset($_POST['parent_comment_id']) ? (int) $_POST['parent_comment_id'] : 0;
    $post_id   = isset($_POST['post_id']) ? (int) $_POST['post_id'] : 0;
    $content   = isset($_POST['content']) ? sanitize_textarea_field(wp_unslash($_POST['content'])) : '';
    if (! $parent_id || ! $post_id || $content === ''){
        wp_send_json_error(['message' => __('Missing fields.', 'dreams-ai')], 400);
    }
    $post = get_post($post_id);
    if (! $post || $post->post_type !== 'service'){
        wp_send_json_error(['message' => __('Invalid service.', 'dreams-ai')], 404);
    }
    $user = wp_get_current_user();
    $is_owner = ((int)$post->post_author === (int)$user->ID);
    $can_moderate = current_user_can('moderate_comments');
    if (! $is_owner && ! $can_moderate){
        wp_send_json_error(['message' => __('You are not allowed to reply to this review.', 'dreams-ai')], 403);
    }
    if ('open' !== get_post_field('comment_status', $post_id)){
        wp_update_post(['ID' => $post_id, 'comment_status' => 'open']);
    }
    $author  = $user->display_name ?: $user->user_login;
    $email   = $user->user_email ?: '';
    $commentdata = array(
        'comment_post_ID'      => $post_id,
        'comment_parent'       => $parent_id,
        'comment_content'      => $content,
        'user_id'              => (int) $user->ID,
        'comment_author'       => $author,
        'comment_author_email' => $email,
        'comment_approved'     => 1,
    );
    $new_id = wp_insert_comment($commentdata);
    if (! $new_id || is_wp_error($new_id)){
        wp_send_json_error(['message' => __('Failed to post reply.', 'dreams-ai')], 500);
    }
    wp_send_json_success(['message' => __('Reply posted.', 'dreams-ai')]);
}

// ===== WooCommerce: Add appointments to real WC cart =====
add_action('wp_ajax_ds_wc_add_cart', 'ds_wc_add_cart');
add_action('wp_ajax_nopriv_ds_wc_add_cart', 'ds_wc_add_cart');
function ds_wc_add_cart(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    if (! function_exists('WC') || ! WC()->cart){
        wp_send_json_error(['message' => 'WooCommerce cart unavailable'], 500);
    }
    $raw = isset($_POST['cart']) ? wp_unslash($_POST['cart']) : '';
    $cart = json_decode($raw, true);
    if (! is_array($cart) || empty($cart)){
        wp_send_json_error(['message' => $cart], 400);
    }

    // Try to map service to product. Prefer per-service linked product ID.
    // Fallback: a product with SKU "service-appointment".
    $fallback_product_id = 0;
    if (function_exists('wc_get_product_id_by_sku')){
        $pid = wc_get_product_id_by_sku('service-appointment');
        if ($pid){ $fallback_product_id = (int) $pid; }
    }

    // Collect service IDs already present in the WooCommerce cart
    $existing_service_ids = array();
    foreach (WC()->cart->get_cart() as $cart_item) {
        if (isset($cart_item['ds_service']['service_id'])) {
            $existing_service_ids[] = (int) $cart_item['ds_service']['service_id'];
        }
    }

    $added_any = false;
    foreach ($cart as $key => $item){
        $sid = isset($item['serviceId']) ? (int) $item['serviceId'] : 0;
        if (! $sid) continue;
        // Skip if this service is already in the WooCommerce cart
        if (in_array($sid, $existing_service_ids, true)) {
            continue;
        }
        $post = get_post($sid);
        if (! $post || $post->post_type !== 'service') continue;

        $linked_product_id = (int) get_post_meta($sid, '_linked_product_id', true);
        $product_id = 0;
        if ($linked_product_id){
            $product_id = $linked_product_id;
        } elseif (function_exists('wc_get_product_id_by_sku')) {
            $by_sku = wc_get_product_id_by_sku('service-' . $sid);
            if ($by_sku){ $product_id = (int) $by_sku; }
        }
        if (! $product_id){ $product_id = $fallback_product_id; }
        if (! $product_id){
            wp_send_json_error(['message' => 'Missing product to add to cart. Create a WooCommerce product with SKU "service-appointment" or set meta linked_product_id on the service.'], 400);
        }

        $title = get_the_title($sid);
        if ($title !== ''){
            if (function_exists('mb_convert_case')) { $title = mb_convert_case($title, MB_CASE_TITLE, 'UTF-8'); }
            else { $title = ucwords(strtolower($title)); }
        }
        $price = get_post_meta($sid, 'service_offer_price', true);
        if ($price === '' || $price === null){ $price = get_post_meta($sid, 'service_price', true); }
        $price = is_numeric($price) ? (float) $price : 0.0;
        $dur_h = (int) get_post_meta($sid, 'service_duration_hours', true);
        $dur_m = (int) get_post_meta($sid, 'service_duration_minutes', true);
        $dur_min = ($dur_h * 60) + $dur_m;

        $addons = array();
        if (!empty($item['addonsCustom']) && is_array($item['addonsCustom'])){
            foreach ($item['addonsCustom'] as $a){
                $ttl = isset($a['title']) ? sanitize_text_field($a['title']) : '';
                $apr = isset($a['price']) ? (float) preg_replace('/[^0-9.\-]/','', (string)$a['price']) : 0.0;
                $ah  = isset($a['dur_h']) ? (int)$a['dur_h'] : 0;
                $am  = isset($a['dur_m']) ? (int)$a['dur_m'] : 0;
                $addons[] = array(
                    'title' => $ttl,
                    'price' => $apr,
                    'dur_h' => $ah,
                    'dur_m' => $am,
                );
            }
        }
        $addons_total_price = 0.0; $addons_total_min = 0;
        foreach ($addons as $a){
            $addons_total_price += (float) $a['price'];
            $addons_total_min   += ((int)$a['dur_h'] * 60 + (int)$a['dur_m']);
        }

        $meta = isset($item['meta']) && is_array($item['meta']) ? $item['meta'] : array();
        $thumb = isset($meta['thumb']) ? esc_url_raw($meta['thumb']) : '';

        $staff = isset($item['staff']) ? (int) $item['staff'] : 0;
        $date  = isset($item['date']) ? sanitize_text_field($item['date']) : '';
        $slot  = isset($item['slot']) ? sanitize_text_field($item['slot']) : '';
        $amen  = isset($item['amenities']) && is_array($item['amenities']) ? array_map('intval', $item['amenities']) : array();
        $branch_id = isset($item['branchId']) ? (int) $item['branchId'] : 0;
        $branch_title = isset($item['branchTitle']) ? sanitize_text_field($item['branchTitle']) : '';
        $branch_location = isset($item['branchLocation']) ? sanitize_text_field($item['branchLocation']) : '';

        $cart_item_data = array(
            'ds_service' => array(
                'service_id' => $sid,
                'service_title' => $title,
                'base_price' => $price,
                'base_dur_min' => $dur_min,
                'addons' => $addons,
                'addons_price' => $addons_total_price,
                'addons_dur_min' => $addons_total_min,
                'staff' => $staff,
                'date'  => $date,
                'slot'  => $slot,
                'amenities' => $amen,
                'thumb' => $thumb,
                'branch_id' => $branch_id,
                'branch_title' => $branch_title,
                'branch_location' => $branch_location,
            )
        );

        $added = WC()->cart->add_to_cart($product_id, 1, 0, array(), $cart_item_data);
        if ($added){
            $added_any = true;
            // Track this service as now present in the cart to avoid adding duplicates in this request
            $existing_service_ids[] = $sid;
        }
    }

    if (! $added_any){
        wp_send_json_error(['message' => 'Nothing added.'], 400);
    }
    wp_send_json_success(['message' => 'Added to cart']);
}

function ds_validate_service_cart_items($cart = null){
    if (! function_exists('WC')){
        return ['valid' => true, 'errors' => []];
    }
    if ($cart === null && WC()->cart){
        $cart = WC()->cart;
    }
    if (! $cart || ! method_exists($cart, 'get_cart')){
        return ['valid' => true, 'errors' => []];
    }
    $errors = [];
    foreach ($cart->get_cart() as $cart_item){
        if (empty($cart_item['ds_service']) || ! is_array($cart_item['ds_service'])){
            continue;
        }
        $ds    = $cart_item['ds_service'];
        $sid   = isset($ds['service_id']) ? (int) $ds['service_id'] : 0;
        $date  = isset($ds['date']) ? sanitize_text_field($ds['date']) : '';
        $slot  = isset($ds['slot']) ? sanitize_text_field($ds['slot']) : '';
        $staff = isset($ds['staff']) ? (int) $ds['staff'] : 0;

        $booking_url = ds_get_booking_page_url($sid);
        $edit_link   = $booking_url ? sprintf(
            ' <a class="ds-edit-selection" href="%s">%s</a>',
            esc_url($booking_url),
            esc_html__('Edit selection', 'dreams-ai')
        ) : '';

        if (! $sid || $date === '' || $slot === ''){
            $errors[] = __('One of your appointments is missing a date or time slot. Please reselect it before placing the order.', 'dreams-ai') . $edit_link;
            continue;
        }

        $post = get_post($sid);
        if (! $post || $post->post_type !== 'service'){
            $errors[] = __('A selected service is no longer available. Please remove it from your cart.', 'dreams-ai') . $edit_link;
            continue;
        }

        $available = ds_get_available_slots_for_service_date($sid, $date, $staff, $post);
        if (! in_array($slot, $available, true)){
            $title = get_the_title($sid);
            if ($title !== ''){
                $title = function_exists('mb_convert_case') ? mb_convert_case($title, MB_CASE_TITLE, 'UTF-8') : ucwords(strtolower($title));
            } else {
                $title = __('service', 'dreams-ai');
            }
            $errors[] = sprintf(
                __('The slot %1$s on %2$s for %3$s is no longer available. Please pick a different slot.', 'dreams-ai'),
                $slot,
                $date,
                $title
            ) . $edit_link;
        }
    }

    return [
        'valid'  => empty($errors),
        'errors' => $errors,
    ];
}

add_action('wp_ajax_ds_validate_cart_slots', 'ds_ajax_validate_cart_slots');
add_action('wp_ajax_nopriv_ds_validate_cart_slots', 'ds_ajax_validate_cart_slots');
function ds_ajax_validate_cart_slots(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    $result = ds_validate_service_cart_items();
    if ($result['valid']){
        wp_send_json_success();
    }
    $message = implode(' ', array_map('wp_strip_all_tags', $result['errors']));
    if ($message === ''){
        $message = __('Selected slots are no longer available. Please update your booking.', 'dreams-ai');
    }
    wp_send_json_error(['message' => $message]);
}

add_action('woocommerce_checkout_process', 'ds_checkout_validate_service_slots');
function ds_checkout_validate_service_slots(){
    if (! function_exists('WC')){
        return;
    }
    $result = ds_validate_service_cart_items();
    if ($result['valid']){
        return;
    }
    foreach ($result['errors'] as $error){
        if (function_exists('wc_add_notice')){
            wc_add_notice($error, 'error');
        }
    }
}

// Display service meta under cart line items
add_filter('woocommerce_get_item_data', function($item_data, $cart_item){
    if (isset($cart_item['ds_service']) && is_array($cart_item['ds_service'])){
        $ds = $cart_item['ds_service'];
        $sid = isset($ds['service_id']) ? (int) $ds['service_id'] : 0;

        // Service
        if (!empty($ds['service_title'])){
            $svc = sanitize_text_field($ds['service_title']);
            if ($svc !== ''){ $svc = function_exists('mb_convert_case') ? mb_convert_case($svc, MB_CASE_TITLE, 'UTF-8') : ucwords(strtolower($svc)); }
            $item_data[] = array('name' => __('Service','dreams-ai'), 'value' => $svc);
        }
        // Date / Time
        if (!empty($ds['date'])){
            $item_data[] = array('name' => __('Date','dreams-ai'), 'value' => sanitize_text_field($ds['date']));
        }
        if (!empty($ds['slot'])){
            $item_data[] = array('name' => __('Time Slot','dreams-ai'), 'value' => sanitize_text_field($ds['slot']));
        }
        // Staff name
        if (!empty($ds['staff'])){
            $name = '';
            $u = get_user_by('id', (int)$ds['staff']);
            if ($u && !is_wp_error($u)){
                $name = $u->display_name ?: ('#'.(int)$ds['staff']);
            } else {
                $name = '#'.(int)$ds['staff'];
            }
            if ($name !== ''){ $name = function_exists('mb_convert_case') ? mb_convert_case($name, MB_CASE_TITLE, 'UTF-8') : ucwords(strtolower($name)); }
            $item_data[] = array('name' => __('Staff','dreams-ai'), 'value' => sanitize_text_field($name));
        }
        // Branch (selected)
        $branch_label = '';
        if (!empty($ds['branch_title'])){
            $branch_label = sanitize_text_field($ds['branch_title']);
        } elseif ($sid){
            $branches = wp_get_object_terms($sid, 'service_branch');
            if (!is_wp_error($branches) && !empty($branches)){
                $branch_label = $branches[0]->name;
            }
        }
        if ($branch_label !== ''){
            if (function_exists('mb_convert_case')){ $branch_label = mb_convert_case($branch_label, MB_CASE_TITLE, 'UTF-8'); }
            else { $branch_label = ucwords(strtolower($branch_label)); }
            $item_data[] = array('name' => __('Branch','dreams-ai'), 'value' => sanitize_text_field($branch_label));
        }
        // Category (first)
        if ($sid){
            $cats = wp_get_object_terms($sid, 'service_category');
            if (!is_wp_error($cats) && !empty($cats)){
                $cn = $cats[0]->name;
                if ($cn !== ''){ $cn = function_exists('mb_convert_case') ? mb_convert_case($cn, MB_CASE_TITLE, 'UTF-8') : ucwords(strtolower($cn)); }
                $item_data[] = array('name' => __('Category','dreams-ai'), 'value' => sanitize_text_field($cn));
            }
        }
        // Add-ons list
        if (!empty($ds['addons']) && is_array($ds['addons'])){
            $names = array();
            foreach ($ds['addons'] as $a){
                $t = isset($a['title']) ? sanitize_text_field($a['title']) : '';
                if ($t !== ''){ $t = function_exists('mb_convert_case') ? mb_convert_case($t, MB_CASE_TITLE, 'UTF-8') : ucwords(strtolower($t)); }
                if ($t !== '') $names[] = $t;
            }
            if (!empty($names)){
                $item_data[] = array('name' => __('Add-ons','dreams-ai'), 'value' => implode(', ', $names));
            }
        }
    }
    return $item_data;
}, 10, 2);

// Adjust cart item price to include base + addons
add_action('woocommerce_before_calculate_totals', function($cart){
    if (is_admin() && ! defined('DOING_AJAX')) return;
    if (empty($cart) || ! isset($cart->cart_contents)) return;
    foreach ($cart->get_cart() as $cart_item_key => $cart_item){
        if (isset($cart_item['ds_service']) && is_array($cart_item['ds_service'])){
            $ds = $cart_item['ds_service'];
            $base = isset($ds['base_price']) ? (float) $ds['base_price'] : 0.0;
            $addons = isset($ds['addons_price']) ? (float) $ds['addons_price'] : 0.0;
            $price = $base + $addons;
            if (isset($cart_item['data']) && is_object($cart_item['data'])){
                $cart_item['data']->set_price($price);
            }
        }
    }
}, 20, 1);

// Persist ds_service to order items
add_action('woocommerce_checkout_create_order_line_item', function($item, $cart_item_key, $values, $order){
    if (isset($values['ds_service']) && is_array($values['ds_service'])){
        $ds = $values['ds_service'];
        if (!empty($ds['service_id'])) $item->add_meta_data('ds_service_id', (int)$ds['service_id'], true);
        if (!empty($ds['service_title'])) $item->add_meta_data('ds_service_title', sanitize_text_field($ds['service_title']), true);
        if (isset($ds['staff'])) $item->add_meta_data('ds_staff_id', (int)$ds['staff'], true);
        if (!empty($ds['date'])) $item->add_meta_data('ds_date', sanitize_text_field($ds['date']), true);
        if (!empty($ds['slot'])) $item->add_meta_data('ds_slot', sanitize_text_field($ds['slot']), true);
        if (isset($ds['addons'])) $item->add_meta_data('ds_addons', wp_json_encode($ds['addons']), true);
        if (isset($ds['base_price'])) $item->add_meta_data('ds_base_price', (float)$ds['base_price'], true);
        if (isset($ds['addons_price'])) $item->add_meta_data('ds_addons_price', (float)$ds['addons_price'], true);
        if (isset($ds['branch_id'])) $item->add_meta_data('ds_branch_id', (int)$ds['branch_id'], true);
        if (!empty($ds['branch_title'])) $item->add_meta_data('ds_branch_title', sanitize_text_field($ds['branch_title']), true);
        if (!empty($ds['branch_location'])) $item->add_meta_data('ds_branch_location', sanitize_text_field($ds['branch_location']), true);
    }
}, 10, 4);

// Insert booking rows into custom table after order is placed

// Ensure ds_bookings table exists at runtime (in case plugin was activated before table code was added)
add_action('init', function(){
    global $wpdb;
    $table = $wpdb->prefix . 'ds_bookings';
    $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
    if ($exists !== $table){
        if (! function_exists('dbDelta')){ require_once ABSPATH . 'wp-admin/includes/upgrade.php'; }
        $charset_collate = $wpdb->get_charset_collate();
        $sql = "CREATE TABLE IF NOT EXISTS `$table` (
          `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
          `order_id` bigint(20) unsigned NOT NULL DEFAULT 0,
          `order_item_id` bigint(20) unsigned NOT NULL DEFAULT 0,
          `product_id` bigint(20) unsigned NOT NULL DEFAULT 0,
          `service_id` bigint(20) unsigned NOT NULL DEFAULT 0,
          `customer_id` bigint(20) unsigned NOT NULL DEFAULT 0,
          `addons` longtext NULL,
          `staff_id` bigint(20) unsigned NOT NULL DEFAULT 0,
          `date` date NULL,
          `slot` varchar(50) NULL,
          `branch_id` bigint(20) unsigned NOT NULL DEFAULT 0,
          `branch_title` varchar(190) NOT NULL DEFAULT '',
          `branch_location` varchar(255) NOT NULL DEFAULT '',
          `amount` decimal(18,2) NOT NULL DEFAULT 0.00,
          `currency` varchar(10) NOT NULL DEFAULT '',
          `payment_status` varchar(20) NOT NULL DEFAULT '',
          `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
          PRIMARY KEY (`id`),
          KEY `order_id` (`order_id`),
          KEY `service_id` (`service_id`),
          KEY `customer_id` (`customer_id`)
        ) $charset_collate;";
        dbDelta($sql);
    }
});

// Helper to upsert Woo order items into ds_bookings
if (! function_exists('ds_upsert_order_into_ds_bookings')){
 function ds_upsert_order_into_ds_bookings($order_id){
    if (! $order_id) return;
    if (! function_exists('wc_get_order')) return;
    $order = wc_get_order($order_id);
    if (! $order) return;
    global $wpdb;
    $table = $wpdb->prefix . 'ds_bookings';
    $customer_id = (int) $order->get_user_id();
    $currency = method_exists($order,'get_currency') ? $order->get_currency() : '';
    $payment_status = method_exists($order,'get_status') ? $order->get_status() : '';
    foreach ($order->get_items() as $item_id => $item){
        $product_id = (int) $item->get_product_id();
        $service_id = (int) $item->get_meta('ds_service_id', true);
        if (! $service_id) continue;
        $staff_id = (int) $item->get_meta('ds_staff_id', true);
        $date = $item->get_meta('ds_date', true);
        $slot = $item->get_meta('ds_slot', true);
        $addons_json = $item->get_meta('ds_addons', true);
        $branch_id = (int) $item->get_meta('ds_branch_id', true);
        $branch_title = $item->get_meta('ds_branch_title', true);
        $branch_location = $item->get_meta('ds_branch_location', true);
        $base_price = (float) $item->get_meta('ds_base_price', true);
        $addons_price = (float) $item->get_meta('ds_addons_price', true);
        $line_total = (float) $item->get_total();
        $amount = $line_total > 0 ? $line_total : ($base_price + $addons_price);
        $exists = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE order_item_id = %d", $item_id));
        if ($exists){
            $wpdb->update($table, [
                'order_id' => (int)$order_id,
                'product_id' => $product_id,
                'service_id' => $service_id,
                'customer_id' => $customer_id,
                'addons' => $addons_json,
                'staff_id' => $staff_id,
                'date' => $date ?: null,
                'slot' => $slot ?: null,
                'branch_id' => $branch_id,
                'branch_title' => $branch_title,
                'branch_location' => $branch_location,
                'amount' => $amount,
                'currency' => $currency,
                'payment_status' => $payment_status,
            ], [ 'order_item_id' => (int)$item_id ], [
                '%d','%d','%d','%d','%s','%d','%s','%s','%d','%s','%s','%f','%s','%s'
            ], [ '%d' ]);
        } else {
            $wpdb->insert($table, [
                'order_id' => (int)$order_id,
                'order_item_id' => (int)$item_id,
                'product_id' => $product_id,
                'service_id' => $service_id,
                'customer_id' => $customer_id,
                'addons' => $addons_json,
                'staff_id' => $staff_id,
                'date' => $date ?: null,
                'slot' => $slot ?: null,
                'branch_id' => $branch_id,
                'branch_title' => $branch_title,
                'branch_location' => $branch_location,
                'amount' => $amount,
                'currency' => $currency,
                'payment_status' => $payment_status,
            ], [
                '%d','%d','%d','%d','%d','%s','%d','%s','%s','%d','%s','%s','%f','%s','%s'
            ]);
        }
    }
 }
}

// Primary hook after checkout redirect
add_action('woocommerce_thankyou', function($order_id){ ds_upsert_order_into_ds_bookings($order_id); }, 10, 1);

// Fallback hook: when order status changes (for gateways that skip thankyou)
add_action('woocommerce_order_status_changed', function($order_id){ ds_upsert_order_into_ds_bookings($order_id); }, 10, 1);

// Ensure ds_enquiries table exists at runtime
add_action('init', function(){
    global $wpdb;
    $table = $wpdb->prefix . 'ds_enquiries';
    $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
    if ($exists !== $table){
        if (! function_exists('dbDelta')){ require_once ABSPATH . 'wp-admin/includes/upgrade.php'; }
        $charset_collate = $wpdb->get_charset_collate();
        $sql = "CREATE TABLE IF NOT EXISTS `$table` (
          `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
          `service_id` bigint(20) unsigned NOT NULL DEFAULT 0,
          `parent_id` bigint(20) unsigned NOT NULL DEFAULT 0,
          `user_id` bigint(20) unsigned NOT NULL DEFAULT 0,
          `name` varchar(190) NOT NULL DEFAULT '',
          `email` varchar(190) NOT NULL DEFAULT '',
          `phone` varchar(50) NOT NULL DEFAULT '',
          `message` longtext NULL,
          `status` varchar(20) NOT NULL DEFAULT 'open',
          `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
          PRIMARY KEY (`id`),
          KEY `service_id` (`service_id`),
          KEY `parent_id` (`parent_id`),
          KEY `user_id` (`user_id`)
        ) $charset_collate;";
        dbDelta($sql);
    }
});

// ===== Single Service: Submit Enquiry =====
add_action('wp_ajax_ds_submit_enquiry', 'ds_ajax_submit_enquiry');
add_action('wp_ajax_nopriv_ds_submit_enquiry', 'ds_ajax_submit_enquiry');
function ds_ajax_submit_enquiry(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    global $wpdb;
    $table = $wpdb->prefix . 'ds_enquiries';

    $service_id = isset($_POST['service_id']) ? (int) $_POST['service_id'] : 0;
    $name  = isset($_POST['name']) ? sanitize_text_field(wp_unslash($_POST['name'])) : '';
    $email = isset($_POST['email']) ? sanitize_email(wp_unslash($_POST['email'])) : '';
    $phone = isset($_POST['phone']) ? sanitize_text_field(wp_unslash($_POST['phone'])) : '';
    $message = isset($_POST['message']) ? sanitize_textarea_field(wp_unslash($_POST['message'])) : '';

    if (! $service_id || $message === '' || $name === '' || ! is_email($email)){
        wp_send_json_error(['message' => __('Please fill all required fields.', 'dreams-ai')], 400);
    }
    $post = get_post($service_id);
    if (! $post || $post->post_type !== 'service'){
        wp_send_json_error(['message' => __('Invalid service.', 'dreams-ai')], 404);
    }
    $user_id = is_user_logged_in() ? (int) get_current_user_id() : 0;

    $inserted = $wpdb->insert(
        $table,
        [
            'service_id' => $service_id,
            'parent_id'  => 0,
            'user_id'    => $user_id,
            'name'       => $name,
            'email'      => $email,
            'phone'      => $phone,
            'message'    => $message,
            'status'     => 'open',
            'created_at' => current_time('mysql'),
        ],
        ['%d','%d','%d','%s','%s','%s','%s','%s','%s']
    );
    if (false === $inserted){
        wp_send_json_error(['message' => __('Failed to submit enquiry. Please try again.', 'dreams-ai')], 500);
    }
    wp_send_json_success(['message' => __('Enquiry submitted successfully.', 'dreams-ai')]);
}

// ===== Enquiry: Submit Reply (threaded conversation) =====
add_action('wp_ajax_ds_submit_enquiry_reply', 'ds_ajax_submit_enquiry_reply');
add_action('wp_ajax_ds_reply_enquiry', 'ds_ajax_submit_enquiry_reply');
function ds_ajax_submit_enquiry_reply(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    if (! is_user_logged_in()){
        wp_send_json_error(['message' => 'Unauthorized'], 401);
    }
    global $wpdb;
    $table = $wpdb->prefix . 'ds_enquiries';
    $parent_id = isset($_POST['enquiry_id']) ? (int) $_POST['enquiry_id'] : 0;
    $message   = isset($_POST['message']) ? sanitize_textarea_field(wp_unslash($_POST['message'])) : '';
    if (! $parent_id || $message === ''){
        wp_send_json_error(['message' => __('Missing fields.', 'dreams-ai')], 400);
    }
    // Load parent to get service_id
    $parent = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $parent_id));
    if (! $parent){
        wp_send_json_error(['message' => __('Enquiry not found.', 'dreams-ai')], 404);
    }
    $user = wp_get_current_user();
    $user_id = (int) $user->ID;
    $name = $user->display_name ?: $user->user_login;
    $email = $user->user_email ?: '';

    // Permission: only service author, editors, or admins
    $service_post = get_post( (int) $parent->service_id );
    if (! $service_post || $service_post->post_type !== 'service'){
        wp_send_json_error(['message' => __('Invalid service.', 'dreams-ai')], 404);
    }
    $can_reply = ((int)$service_post->post_author === $user_id) || current_user_can('edit_post', $service_post->ID) || current_user_can('manage_options');
    if (! $can_reply){
        wp_send_json_error(['message' => __('You do not have permission to reply to this enquiry.', 'dreams-ai')], 403);
    }

    $inserted = $wpdb->insert(
        $table,
        [
            'service_id' => (int) $parent->service_id,
            'parent_id'  => $parent_id,
            'user_id'    => $user_id,
            'name'       => $name,
            'email'      => $email,
            'phone'      => '',
            'message'    => $message,
            'status'     => 'open',
            'created_at' => current_time('mysql'),
        ],
        ['%d','%d','%d','%s','%s','%s','%s','%s','%s']
    );
    if (false === $inserted){
        wp_send_json_error(['message' => __('Failed to save reply. Please try again.', 'dreams-ai')], 500);
    }
    // Email notify original enquirer if email exists
    $to = ! empty($parent->email) && is_email($parent->email) ? $parent->email : '';
    if ($to){
        $service = get_post((int) $parent->service_id);
        $service_title = $service ? get_the_title($service) : __('Service','dreams-ai');
        $blogname = wp_specialchars_decode( get_option('blogname'), ENT_QUOTES );
        $subject = sprintf( __('Reply to your enquiry - %s', 'dreams-ai'), $service_title );
        $body  = '<p>' . sprintf( __('Hello %s,', 'dreams-ai'), esc_html($parent->name) ) . '</p>';
        $body .= '<p>' . __('You have received a reply to your enquiry:', 'dreams-ai') . '</p>';
        $body .= '<blockquote>' . nl2br( esc_html($message) ) . '</blockquote>';
        $link = get_permalink( (int) $parent->service_id );
        if ($link){ $body .= '<p><a href="' . esc_url($link) . '">' . __('View Service', 'dreams-ai') . '</a></p>'; }
        $body .= '<p>' . sprintf( __('Regards, %s', 'dreams-ai'), esc_html($blogname) ) . '</p>';
        $headers = array('Content-Type: text/html; charset=UTF-8');
        wp_mail($to, $subject, $body, $headers);
    }
    wp_send_json_success(['message' => __('Reply posted.', 'dreams-ai')]);
}

// ===== Enquiry: Get Thread (root + replies) =====
add_action('wp_ajax_ds_get_enquiry_thread', 'ds_ajax_get_enquiry_thread');
function ds_ajax_get_enquiry_thread(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    if (! is_user_logged_in()){
        wp_send_json_error(['message' => 'Unauthorized'], 401);
    }
    global $wpdb;
    $table = $wpdb->prefix . 'ds_enquiries';
    $enquiry_id = isset($_POST['enquiry_id']) ? (int) $_POST['enquiry_id'] : 0;
    if (! $enquiry_id){
        wp_send_json_error(['message' => __('Missing enquiry id.', 'dreams-ai')], 400);
    }
    $root = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $enquiry_id));
    if (! $root){
        wp_send_json_error(['message' => __('Enquiry not found.', 'dreams-ai')], 404);
    }
    // Permission: only service author, editors, or admins can view
    $user = wp_get_current_user();
    $service_post = get_post( (int) $root->service_id );
    if (! $service_post || $service_post->post_type !== 'service'){
        wp_send_json_error(['message' => __('Invalid service.', 'dreams-ai')], 404);
    }
    $can_view = ((int)$service_post->post_author === (int)$user->ID) || current_user_can('edit_post', $service_post->ID) || current_user_can('manage_options');
    if (! $can_view){
        wp_send_json_error(['message' => __('You do not have permission to view this enquiry.', 'dreams-ai')], 403);
    }

    $replies = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$table} WHERE parent_id = %d ORDER BY created_at ASC", $enquiry_id));
    $fmt = function($row){
        $ts = strtotime($row->created_at);
        $name = (string) ($row->name ?: '');
        if (function_exists('dreamsai_sentencecase')){ $name = dreamsai_sentencecase($name); }
        return array(
            'id'      => (int) $row->id,
            'name'    => $name,
            'email'   => (string) ($row->email ?: ''),
            'created' => $ts ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $ts) : '',
            'message' => (string) ($row->message ?: ''),
            'status'  => (string) ($row->status ?: 'open'),
        );
    };

    $payload = array(
        'root'    => $fmt($root),
        'replies' => array_map($fmt, $replies ?: array()),
    );
    wp_send_json_success($payload);
}

// ===== Enquiry: Set Status (open/closed) =====
add_action('wp_ajax_ds_set_enquiry_status', 'ds_ajax_set_enquiry_status');
function ds_ajax_set_enquiry_status(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    if (! is_user_logged_in()){
        wp_send_json_error(['message' => 'Unauthorized'], 401);
    }
    global $wpdb; $table = $wpdb->prefix . 'ds_enquiries';
    $enquiry_id = isset($_POST['enquiry_id']) ? (int) $_POST['enquiry_id'] : 0;
    $status     = isset($_POST['status']) ? sanitize_text_field(wp_unslash($_POST['status'])) : '';
    if (! $enquiry_id || ! in_array($status, array('open','closed'), true)){
        wp_send_json_error(['message' => __('Invalid parameters.', 'dreams-ai')], 400);
    }
    $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $enquiry_id));
    if (! $row){
        wp_send_json_error(['message' => __('Enquiry not found.', 'dreams-ai')], 404);
    }
    $user = wp_get_current_user();
    $post = get_post( (int) $row->service_id );
    $can = $post && ($post->post_type==='service') && ( (int)$post->post_author === (int)$user->ID || current_user_can('edit_post', $post->ID) || current_user_can('manage_options') );
    if (! $can){
        wp_send_json_error(['message' => __('Permission denied.', 'dreams-ai')], 403);
    }
    $ok = $wpdb->update($table, array('status' => $status), array('id' => (int)$enquiry_id), array('%s'), array('%d'));
    if ($ok === false){
        wp_send_json_error(['message' => __('Failed to update status.', 'dreams-ai')], 500);
    }
    wp_send_json_success(array('status' => $status));
}

// AJAX: fetch bookings list for dashboard
add_action('wp_ajax_ds_get_bookings', function(){
    if (! current_user_can('manage_options') && ! current_user_can('agent')){
        wp_send_json_error(['message' => 'Unauthorized'], 401);
    }
    global $wpdb;
    $table = $wpdb->prefix . 'ds_bookings';
    $rows = $wpdb->get_results("SELECT * FROM {$table} ORDER BY id DESC LIMIT 200", ARRAY_A);
    wp_send_json_success(['rows' => $rows]);
});

function ds_get_available_slots_for_service_date($service_id, $date, $staff_id = 0, $post = null) {
    $service_id = (int) $service_id;
    $staff_id   = (int) $staff_id;
    $date       = sanitize_text_field($date);

    if (! $service_id || $date === '') {
        return array();
    }

    if (! $post) {
        $post = get_post($service_id);
    }
    if (! $post || $post->post_type !== 'service') {
        return array();
    }

    $ts = strtotime($date);
    if (! $ts) {
        return array();
    }

    $weekday_idx = (int) date('N', $ts); // 1 (Mon) .. 7 (Sun)
    $keys = [1=>'mon',2=>'tue',3=>'wed',4=>'thu',5=>'fri',6=>'sat',7=>'sun'];
    $wkey = isset($keys[$weekday_idx]) ? $keys[$weekday_idx] : 'mon';

    $slots_cfg = get_post_meta($service_id, 'service_slots_config', true);
    $slots = array();
    if (is_array($slots_cfg) && !empty($slots_cfg[$wkey]) && !empty($slots_cfg[$wkey]['time_slots'])) {
        $slots = array_values(array_map('sanitize_text_field', (array) $slots_cfg[$wkey]['time_slots']));
    } else {
        $slots = (array) get_post_meta($service_id, 'service_slots_times', true);
        $slots = array_values(array_map('sanitize_text_field', $slots));
    }

    global $wpdb;
    $table = $wpdb->prefix . 'ds_bookings';
    $block_statuses = array('pending','processing','on-hold','completed');
    $placeholders   = implode(',', array_fill(0, count($block_statuses), '%s'));

    if ($staff_id > 0) {
        $query = $wpdb->prepare(
            "SELECT slot FROM {$table} WHERE service_id = %d AND date = %s AND staff_id = %d AND payment_status IN ($placeholders)",
            array_merge(array($service_id, $date, $staff_id), $block_statuses)
        );
    } else {
        $query = $wpdb->prepare(
            "SELECT slot FROM {$table} WHERE service_id = %d AND date = %s AND payment_status IN ($placeholders)",
            array_merge(array($service_id, $date), $block_statuses)
        );
    }

    $booked = $wpdb->get_col($query);
    if (!is_wp_error($booked) && !empty($booked)) {
        $booked = array_map('sanitize_text_field', (array) $booked);
        $slots  = array_values(array_diff($slots, $booked));
    }

    $today = current_time('Y-m-d');
    if ($date === $today && !empty($slots)) {
        $now_ts = current_time('timestamp');
        $current_minutes = (int) date('G', $now_ts) * 60 + (int) date('i', $now_ts);
        $filtered = array();
        foreach ($slots as $slot) {
            $raw = trim((string) $slot);
            if ($raw === '') {
                continue;
            }
            $norm = preg_replace('/\s+to\s+/i', '-', $raw);
            if (!preg_match('/(\d{1,2})(?::(\d{2}))?\s*(AM|PM)?/i', $norm, $m)) {
                $filtered[] = $slot;
                continue;
            }
            $h = (int) $m[1];
            $min = isset($m[2]) ? (int) $m[2] : 0;
            $ampm = isset($m[3]) ? strtoupper($m[3]) : '';
            if ($ampm === 'PM' && $h < 12) {
                $h += 12;
            } elseif ($ampm === 'AM' && $h === 12) {
                $h = 0;
            }
            $slot_minutes = $h * 60 + $min;
            if ($slot_minutes > $current_minutes) {
                $filtered[] = $slot;
            }
        }
        $slots = $filtered;
    }

    return $slots;
}

// ===== Frontend Booking: Get slots for a specific date =====
add_action('wp_ajax_ds_get_service_slots_for_date', 'ds_ajax_get_service_slots_for_date');
add_action('wp_ajax_nopriv_ds_get_service_slots_for_date', 'ds_ajax_get_service_slots_for_date');
function ds_ajax_get_service_slots_for_date(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    $sid = isset($_POST['service_id']) ? (int) $_POST['service_id'] : 0;
    $date = isset($_POST['date']) ? sanitize_text_field($_POST['date']) : '';
    $staff_id = isset($_POST['staff_id']) ? (int) $_POST['staff_id'] : 0;
    if (! $sid || ! $date){
        wp_send_json_error(['message' => 'Missing parameters'], 400);
    }
    $post = get_post($sid);
    if (! $post || $post->post_type !== 'service'){
        wp_send_json_error(['message' => 'Not found'], 404);
    }
    $ts = strtotime($date);
    if (! $ts){
        wp_send_json_error(['message' => 'Invalid date'], 400);
    }

    $slots_data = ds_resolve_service_slots_for_date($sid, $date, $staff_id, $post);
    $slots = $slots_data['slots'];
    $assigned_staff_id = $slots_data['assigned_staff_id'];
    $next_available_date = '';

    if (empty($slots)) {
        $ts = strtotime($date);
        if ($ts) {
            for ($i = 1; $i <= 30; $i++) {
                $candidate_ts = strtotime('+' . $i . ' days', $ts);
                if (! $candidate_ts) {
                    continue;
                }
                $candidate_date = date('Y-m-d', $candidate_ts);
                $candidate_data = ds_resolve_service_slots_for_date($sid, $candidate_date, $staff_id, $post);
                if (!empty($candidate_data['slots'])) {
                    $next_available_date = $candidate_date;
                    break;
                }
            }
        }
    }

    wp_send_json_success([
        'slots' => $slots,
        'assigned_staff_id' => $assigned_staff_id,
        'next_available_date' => $next_available_date,
    ]);
}
// ===== Frontend Booking: Build Service Booking Drawer Content =====
add_action('wp_ajax_ds_get_service_booking', 'ds_ajax_get_service_booking');
add_action('wp_ajax_nopriv_ds_get_service_booking', 'ds_ajax_get_service_booking');
function ds_ajax_get_service_booking(){
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')){
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    $sid = isset($_POST['service_id']) ? (int) $_POST['service_id'] : 0;
    $post = $sid ? get_post($sid) : null;
    if (! $post || $post->post_type !== 'service' || $post->post_status !== 'publish'){
        wp_send_json_error(['message' => 'Not found'], 404);
    }

    $title = get_the_title($sid);
    $price = get_post_meta($sid, 'service_price', true);
    $offer = get_post_meta($sid, 'service_offer_price', true);
    $dur_h = (int) get_post_meta($sid, 'service_duration_hours', true);
    $dur_m = (int) get_post_meta($sid, 'service_duration_minutes', true);
    $dur_parts = [];
    if ($dur_h > 0){ $dur_parts[] = $dur_h . ' Hrs'; }
    if ($dur_m > 0){ $dur_parts[] = $dur_m . ' Mins'; }
    $dur_text = !empty($dur_parts) ? implode(' ', $dur_parts) : '';
    $dur_total_min = ($dur_h * 60) + $dur_m;

    // Select display/base price (prefer offer)
    $base_price = ($offer !== '' ? $offer : $price);
    $currency_symbol = function_exists('get_woocommerce_currency_symbol') ? get_woocommerce_currency_symbol() : '$';

    // Thumb (prefer first gallery)
    $thumb = '';
    $gallery_ids = get_post_meta($sid, 'service_gallery', true);
    if (is_array($gallery_ids) && !empty($gallery_ids)){
        $first_id = (int) reset($gallery_ids);
        $img = wp_get_attachment_image_url($first_id, 'medium');
        if ($img) { $thumb = $img; }
    }
    if (!$thumb){
        $fi = get_the_post_thumbnail_url($sid, 'medium');
        if ($fi) { $thumb = $fi; }
    }
    if (! $thumb){ $thumb = 'https://via.placeholder.com/300x200?text=Service'; }

    $staff_ids = (array) get_post_meta($sid, 'service_staff_users', true);
    $staff_ids = array_filter(array_map('intval', $staff_ids));
    $staff_users = [];
    if (!empty($staff_ids)){
        $staff_users = get_users(['include' => $staff_ids, 'fields' => ['ID','display_name']]);
    }

    $amenity_terms = get_the_terms($sid, 'service_amenities');
    if (is_wp_error($amenity_terms)) $amenity_terms = [];

    $slots_flat = (array) get_post_meta($sid, 'service_slots_times', true);

    ob_start();
   echo '<div class="ds-booking-drawer-content" data-service-id="' . (int)$sid . '">';
    echo '  <div class="border-bottom d-flex align-items-center justify-content-between pt-0 px-0 pb-3 mb-3">';
    echo '    <h4 class="offcanvas-title">' . esc_html__('Service Booking', 'dreams-ai') . '</h5>';
    echo '    <button type="button" class="btn1 btn-close btn-link text-dark p-0 ds-drawer-close"><i class="ti ti-x"></i></button>';
    echo '  </div>';
    // Hidden meta for JS to compute totals and cart render
    echo '  <div id="ds-service-meta" class="d-none"'
        . ' data-title="' . esc_attr($title) . '"'
        . ' data-price="' . esc_attr($base_price) . '"'
        . ' data-dur-min="' . esc_attr($dur_total_min) . '"'
        . ' data-thumb="' . esc_url($thumb) . '"></div>';

    echo '  <div class="btn-group w-100 nav nav-tabs tab-dark nav-justified border-0 gap-3 mb-4" role="group">';
    echo '<div class="nav-item"><button type="button" class="btn btn-outline-dark w-100 active" data-step="addons">' . esc_html__('Add Ons & Staff','dreams-ai') . '</button></div>';
    echo '<div class="nav-item"><button type="button" class="btn btn-outline-dark w-100 " data-step="datetime">' . esc_html__('Date & Time','dreams-ai') . '</button></div>';
    echo '  </div>';

    echo '  <div class="">';
    echo '    <div class="mb-3 ds-step ds-step-addons">';
    echo '      <h6 class="mb-2">' . esc_html__('Select Add ons', 'dreams-ai') . '</h6>';
    // Custom Add Ons
    echo '      <div class="mb-2"><strong>' . esc_html__('Add Ons', 'dreams-ai') . '</strong></div>';
    echo '      <div class="row g-2 mb-3">';
    $service_addons = (array) get_post_meta($sid, 'service_addons', true);
    if (!empty($service_addons)){
        foreach ($service_addons as $idx => $row){
            $ttl = isset($row['title']) ? $row['title'] : '';
            if ($ttl !== ''){
                if (function_exists('mb_convert_case')) { $ttl = mb_convert_case($ttl, MB_CASE_TITLE, 'UTF-8'); }
                else { $ttl = ucwords(strtolower($ttl)); }
            }
            $pr  = isset($row['price']) ? $row['price'] : '';
            $currency_symbol = function_exists('get_woocommerce_currency_symbol') ? get_woocommerce_currency_symbol() : '$';

            $dh  = isset($row['dur_h']) ? (int)$row['dur_h'] : 0;
            $dm  = isset($row['dur_m']) ? (int)$row['dur_m'] : 0;
            $dtx = trim(($dh>0?($dh.'h '):'') . ($dm>0?($dm.'m'):'') );
            $meta = esc_attr(wp_json_encode($row));
            $price_label = '';
            if ($pr !== ''){
                if (function_exists('wc_price') && is_numeric($pr)){
                    $price_label = wp_strip_all_tags( wc_price((float)$pr) );
                } else {
                    $price_label = $currency_symbol . $pr;
                }
            }
            echo '<div class="col-6">'
                . '<button type="button" class="btn w-100 text-start border ds-addon-chip" data-addon="' . $meta . '">' 
                . '<div class="d-flex justify-content-between flex-column">'
                . '<span>' . esc_html($ttl) . '</span>'
                . '<small class="text-muted">' . esc_html($price_label) . ($dtx? ' / '.$dtx : '') . '</small>'
                . '</div>'
                . '</button>'
                . '</div>';
        }
    } else {
        echo '<div class="col-12"><em>' . esc_html__('No custom add-ons configured for this service.', 'dreams-ai') . '</em></div>';    
    }
    echo '      </div>';


    echo '    <div class="mb-3">';
    echo '      <div class="d-flex justify-content-between align-items-center mb-2">';
    echo '        <h6 class="mb-0">' . esc_html__('Select Your Specialist', 'dreams-ai') . '</h6>';
    echo '        <button type="button" class="btn btn-sm btn-light ds-auto-assign">' . esc_html__('Auto Assign','dreams-ai') . '</button>';
    echo '      </div>';
    echo '      <div class="row g-2">';
    if (!empty($staff_users)){
        foreach ($staff_users as $u){
            $avatar = get_avatar_url($u->ID, ['size'=>64]);
            $dn = $u->display_name;
            if ($dn !== ''){
                if (function_exists('mb_convert_case')) { $dn = mb_convert_case($dn, MB_CASE_TITLE, 'UTF-8'); }
                else { $dn = ucwords(strtolower($dn)); }
            }
            echo '<div class="col-6 staff-name-booking"><button type="button" class="btn w-100 d-flex align-items-center border ds-staff-chip" data-user-id="' . (int)$u->ID . '"><img src="' . esc_url($avatar) . '" alt="" class="me-2 rounded-circle" width="32" height="32" /><div class="text-start"><div class="fw-semibold">' . esc_html($dn) . '</div><small class="text-muted staff-speciality">' . esc_html__('Senior Stylist','dreams-ai') . '</small></div></button></div>';
        }
    } else {
        echo '<div class="col-12"><em>' . esc_html__('No staff assigned for this service.', 'dreams-ai') . '</em></div>';
    }
    echo '      </div>';
    echo '    </div>';
    echo '  </div>';

    echo '  <div class="ds-step ds-step-datetime" style="display:none;">';
    echo '    <div class="mb-3">';
    echo '      <h6 class="mb-2">' . esc_html__('Choose Date', 'dreams-ai') . '</h6>';
    echo '      <input type="text" class="form-control ds-date-picker" style="height:0;opacity:0;position:absolute;left:-9999px;" />';
    echo '      <div class="ds-calendar-inline"></div>';
    echo '    </div>';
    echo '    <div class="mb-3">';
    echo '      <h6 class="mb-2">' . esc_html__('Available Slot', 'dreams-ai') . '</h6>';
    echo '      <div class="row ds-slots-wrap p-2 border rounded" data-service-id="' . (int)$sid . '">';
    echo '        <em>' . esc_html__('Pick a date to see available slots.', 'dreams-ai') . '</em>';
    echo '      </div>';
    echo '    </div>';
    echo '  </div>';

    echo '  <div class="border-top pt-3 d-flex justify-content-between">';
    echo '    <button type="button" class="btn btn-light ds-reset">' . esc_html__('Reset', 'dreams-ai') . '</button>';
    echo '    <button type="button" class="btn btn-dark ds-add-to-cart">' . esc_html__('Add', 'dreams-ai') . '</button>';
    echo '  </div>';
    echo '</div>';
    $html = ob_get_clean();

    wp_send_json_success(['html' => $html]);
}

// ===== Dashboard: Delete Service (admin-post) =====
add_action('admin_post_ds_delete_service', 'ds_handle_delete_service');
function ds_handle_delete_service(){
    if (! is_user_logged_in()){
        wp_safe_redirect( wp_get_referer() ?: home_url('/') );
        exit;
    }
    $post_id = isset($_GET['post_id']) ? (int) $_GET['post_id'] : 0;
    $nonce   = isset($_GET['_wpnonce']) ? $_GET['_wpnonce'] : '';
    $action  = 'ds_delete_service_' . $post_id;
    if (! $post_id || ! wp_verify_nonce($nonce, $action)){
        wp_safe_redirect( add_query_arg('ds_msg', 'invalid_nonce', (wp_get_referer() ?: home_url('/'))) );
        exit;
    }
    $post = get_post($post_id);
    if (! $post || $post->post_type !== 'service'){
        wp_safe_redirect( add_query_arg('ds_msg', 'not_found', (wp_get_referer() ?: home_url('/'))) );
        exit;
    }
    $user = wp_get_current_user();
    $is_owner = ((int)$post->post_author === (int)$user->ID);
    $is_admin = in_array('administrator', (array)$user->roles, true);
    $is_agent = in_array('agent', (array)$user->roles, true);
    if (! $is_owner && ! $is_admin && ! $is_agent){
        wp_safe_redirect( add_query_arg('ds_msg', 'forbidden', (wp_get_referer() ?: home_url('/'))) );
        exit;
    }
    // Prefer trash to allow recovery
    $trashed = wp_trash_post($post_id);
    $msg = $trashed ? 'deleted' : 'failed';
    wp_safe_redirect( add_query_arg('ds_msg', $msg, (wp_get_referer() ?: home_url('/wp-admin/edit.php?post_type=service'))) );
    exit;
}

add_action('wp_ajax_ds_dashboard_load_more', 'ds_ajax_dashboard_load_more');
function ds_ajax_dashboard_load_more() {
    if (! isset($_POST['_ajax_nonce']) || ! wp_verify_nonce($_POST['_ajax_nonce'], 'ds_admin_nonce')) {
      wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    if (! is_user_logged_in()) {
      wp_send_json_error(['message' => 'Unauthorized'], 401);
    }

    $role = isset($_POST['role']) ? sanitize_key(wp_unslash($_POST['role'])) : '';
    $page = isset($_POST['page']) ? max(1, absint($_POST['page'])) : 1;

    global $wpdb;
    $book_table = $wpdb->prefix . 'ds_bookings';

    // Customer dashboard: group by order_id (same as customer bookings template)
    if ($role === 'customer') {
      $current_user = wp_get_current_user();
      $customer_id  = (int) $current_user->ID;
      $per_page     = 1; // keep in sync with customer bookings template

      $where  = ['b.customer_id = %d'];
      $params = [$customer_id];

      $status = isset($_POST['status']) ? sanitize_key(wp_unslash($_POST['status'])) : '';
      if ($status !== '') {
        $where[]  = 'b.payment_status = %s';
        $params[] = $status;
      }

      $where_sql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';

      $count_sql = "SELECT COUNT(DISTINCT b.order_id)
                     FROM $book_table b
                     $where_sql";
      $total_bookings = (int) $wpdb->get_var($wpdb->prepare($count_sql, $params));

      $offset = ($page - 1) * $per_page;

      $rows_sql = "SELECT 
                      MIN(b.id) AS id,
                      b.order_id,
                      MIN(b.date) AS date,
                      MIN(b.slot) AS slot,
                      MIN(b.staff_id) AS staff_id,
                      MIN(b.service_id) AS service_id,
                      MIN(b.payment_status) AS payment_status,
                      SUM(b.amount) AS amount
                    FROM $book_table b
                    $where_sql
                    GROUP BY b.order_id
                    ORDER BY MIN(b.date) DESC, MIN(b.slot) ASC
                    LIMIT %d OFFSET %d";
      $rows = $wpdb->get_results($wpdb->prepare($rows_sql, array_merge($params, [$per_page, $offset])));

      $format_slot = function ($slot) {
        $slot = trim((string) $slot);
        if ($slot === '' || strpos($slot, '-') === false) {
          return esc_html($slot);
        }
        list($s, $e) = array_map('trim', explode('-', $slot, 2));
        $sf = date_i18n('h:i A', strtotime($s));
        $ef = date_i18n('h:i A', strtotime($e));
        return $sf . '<span>-</span> ' . $ef;
      };

      $get_status = function ($order_id, $fallback_status) {
        if (! function_exists('wc_get_order')) {
          return [
            'text'  => ucfirst((string) $fallback_status),
            'class' => 'badge-soft-info',
          ];
        }
        $order = wc_get_order($order_id);
        if (! $order) {
          return [
            'text'  => ucfirst((string) $fallback_status),
            'class' => 'badge-soft-info',
          ];
        }
        $status       = $order->get_status();
        $status_name  = wc_get_order_status_name($status);
        $status_class = [
          'pending'    => 'badge-soft-warning',
          'processing' => 'badge-soft-info',
          'on-hold'    => 'badge-soft-warning',
          'completed'  => 'badge-soft-success',
          'cancelled'  => 'badge-soft-danger',
          'refunded'   => 'badge-soft-danger',
          'failed'     => 'badge-soft-danger',
        ];
        $class = isset($status_class[$status]) ? $status_class[$status] : 'badge-soft-info';
        return ['text' => $status_name, 'class' => $class];
      };

      ob_start();
      if (! empty($rows)) {
        foreach ($rows as $row) {
          $service_id    = (int) $row->service_id;
          $service_title = $service_id ? get_the_title($service_id) : __('Service', 'dreams-ai');
          $service_img   = $service_id ? get_the_post_thumbnail_url($service_id, 'thumbnail') : '';
          $branch        = '';
          if ($service_id) {
            $terms = wp_get_object_terms($service_id, 'service_branch');
            if (! is_wp_error($terms) && ! empty($terms)) {
              $branch = $terms[0]->name;
            }
          }
          $date_label = $row->date ? date_i18n('d M, Y', strtotime($row->date)) : '';
          $slot_html  = $row->slot ? $format_slot($row->slot) : '';

          $order_status = $get_status($row->order_id, $row->payment_status);
          $badge_txt    = $order_status['text'];
          $badge_class  = $order_status['class'];

          $staff_avatar = $row->staff_id ? get_avatar_url((int) $row->staff_id, ['size' => 48]) : '';
          $modal_target = '#appointment_modal_' . (int) $row->id;
          ?>
          <div class="appointments-itemone">
            <div class="appointments-item appointments-itemone">
              <div class="d-flex align-items-center gap-2 flex-wrap">
                <div class="avatar avatar-lg">
                  <?php
                  $service_gallery = get_post_meta($service_id, 'service_gallery', true);
                  if (! empty($service_gallery) && is_array($service_gallery)) {
                    $first_img_id    = (int) $service_gallery[0];
                    $service_img_src = wp_get_attachment_image_src($first_img_id, 'thumbnail');
                    if ($service_img_src) {
                      ?>
                      <img src="<?php echo esc_url($service_img_src[0]); ?>" alt="service" class="rounded-circle">
                      <?php
                    } elseif ($service_img) {
                      ?>
                      <img src="<?php echo esc_url($service_img); ?>" alt="service" class="rounded-circle">
                      <?php
                    } else {
                      ?>
                      <div class="avatar avatar-lg rounded-circle bg-light"></div>
                      <?php
                    }
                  } else {
                    if ($service_img) {
                      ?>
                      <img src="<?php echo esc_url($service_img); ?>" alt="service" class="rounded-circle">
                      <?php
                    } else {
                      ?>
                      <div class="avatar avatar-lg rounded-circle bg-light"></div>
                      <?php
                    }
                  }
                  ?>
                </div>
                <div>
                  <h6 class="fs-16 fw-semibold mb-1 d-flex align-items-center gap-2 flex-wrap text-capitalize">
                    <a href="<?php echo esc_url($service_id ? get_permalink($service_id) : '#'); ?>">
                      <?php
                      $__st = $service_title;
                      echo esc_html(function_exists('dreamsai_sentencecase') ? dreamsai_sentencecase($__st) : $__st);
                      ?>
                    </a>
                    <span class="badge badge-sm <?php echo esc_attr($badge_class); ?>"><?php echo esc_html($badge_txt); ?></span>
                  </h6>
                  <?php if ($branch) : ?>
                    <p class="mb-0 fs-14 text-capitalize">
                      <i class="ti ti-map-pin-bolt me-1 fs-14"></i>
                      <?php
                      $__br = $branch;
                      echo esc_html(function_exists('dreamsai_sentencecase') ? dreamsai_sentencecase($__br) : $__br);
                      ?>
                    </p>
                  <?php endif; ?>
                </div>
              </div>
              <div>
                <h6 class="mb-1 fs-16 fw-semibold"><?php echo esc_html($date_label); ?></h6>
                <p class="fs-14 mb-0 d-flex align-items-center gap-2"><?php echo wp_kses_post($slot_html); ?></p>
              </div>
              <div>
                <p class="fs-14 mb-1"><?php esc_html_e('Assigned Staff', 'dreams-ai'); ?></p>
                <div class="avatar-list-stacked">
                  <?php if ($staff_avatar) : ?>
                    <span class="avatar avatar-sm rounded-circle border-0">
                      <img src="<?php echo esc_url($staff_avatar); ?>" class="img-fluid rounded-circle border border-white" alt="staff">
                    </span>
                  <?php else : ?>
                    <span class="text-muted"><?php esc_html_e('Not assigned', 'dreams-ai'); ?></span>
                  <?php endif; ?>
                </div>
              </div>
              <div class="text-xl-end">
                <a href="#" class="btn btn-sm border border-color btn-hover d-inline-flex align-items-center fs-13 fw-medium" data-bs-toggle="modal" data-bs-target="<?php echo esc_attr($modal_target); ?>">
                  <?php esc_html_e('View Details', 'dreams-ai'); ?>
                  <i class="ti ti-chevron-right ms-1"></i>
                </a>
              </div>
            </div>
          </div>
          <?php
        }
      }
      $html     = ob_get_clean();
      $has_more = ($total_bookings > $page * $per_page);
      wp_send_json_success(['html' => $html, 'has_more' => $has_more]);
    }

    // Agent dashboard: group orders by order_id for services authored by current user
    if ($role === 'agent') {
      $current_user = wp_get_current_user();
      $agent_id     = (int) $current_user->ID;
      $per_page     = 1; // keep in sync with agent bookings template

      $status = isset($_POST['status']) ? sanitize_key(wp_unslash($_POST['status'])) : '';
      $sort   = isset($_POST['sort']) ? sanitize_key(wp_unslash($_POST['sort'])) : 'newest';

      $posts  = $wpdb->posts;
      $where  = ['p.post_author = %d'];
      $params = [$agent_id];

      if ($status !== '') {
        $where[]  = 'b.payment_status = %s';
        $params[] = $status;
      }

      $where_sql    = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
      $order_by_sql = 'ORDER BY MIN(b.date) DESC, MIN(b.slot) ASC';
      switch ($sort) {
        case 'oldest':
          $order_by_sql = 'ORDER BY MIN(b.date) ASC, MIN(b.slot) ASC';
          break;
        case 'alphabetical_az':
          $order_by_sql = 'ORDER BY MAX(p.post_title) ASC';
          break;
        case 'alphabetical_za':
          $order_by_sql = 'ORDER BY MAX(p.post_title) DESC';
          break;
        default:
          $order_by_sql = 'ORDER BY MIN(b.date) DESC, MIN(b.slot) ASC';
          break;
      }

      $count_sql = "SELECT COUNT(DISTINCT b.order_id)
                     FROM $book_table b
                     INNER JOIN $posts p ON p.ID = b.service_id
                     $where_sql";
      $total_bookings = (int) $wpdb->get_var($wpdb->prepare($count_sql, $params));

      $offset = ($page - 1) * $per_page;

      $rows_sql = "SELECT
                      MIN(b.id) AS id,
                      b.order_id,
                      MIN(b.date) AS date,
                      MIN(b.slot) AS slot,
                      MIN(b.staff_id) AS staff_id,
                      MIN(b.service_id) AS service_id,
                      MIN(b.customer_id) AS customer_id,
                      MIN(b.payment_status) AS payment_status,
                      SUM(b.amount) AS amount,
                      MAX(p.post_title) AS post_title
                    FROM $book_table b
                    INNER JOIN $posts p ON p.ID = b.service_id
                    $where_sql
                    GROUP BY b.order_id
                    $order_by_sql
                    LIMIT %d OFFSET %d";
      $rows = $wpdb->get_results($wpdb->prepare($rows_sql, array_merge($params, [$per_page, $offset])));

      $format_slot = function ($slot) {
        $slot = trim((string) $slot);
        if ($slot === '' || strpos($slot, '-') === false) {
          return esc_html($slot);
        }
        list($s, $e) = array_map('trim', explode('-', $slot, 2));
        return date_i18n('h:i A', strtotime($s)) . '<span>-</span> ' . date_i18n('h:i A', strtotime($e));
      };

      $get_status = function ($order_id, $fallback_status) {
        if (! function_exists('wc_get_order')) {
          return [
            'text'  => ucfirst((string) $fallback_status),
            'class' => 'badge-soft-info',
          ];
        }
        $order = wc_get_order($order_id);
        if (! $order) {
          return [
            'text'  => ucfirst((string) $fallback_status),
            'class' => 'badge-soft-info',
          ];
        }
        $status       = $order->get_status();
        $status_name  = wc_get_order_status_name($status);
        $status_class = [
          'pending'    => 'badge-soft-warning',
          'processing' => 'badge-soft-info',
          'on-hold'    => 'badge-soft-warning',
          'completed'  => 'badge-soft-success',
          'cancelled'  => 'badge-soft-danger',
          'refunded'   => 'badge-soft-danger',
          'failed'     => 'badge-soft-danger',
        ];
        $class = isset($status_class[$status]) ? $status_class[$status] : 'badge-soft-info';
        return ['text' => $status_name, 'class' => $class];
      };

      ob_start();
      if (! empty($rows)) {
        foreach ($rows as $row) {
          $service_id    = (int) $row->service_id;
          $service_title = $row->post_title ?: ($service_id ? get_the_title($service_id) : __('Service', 'dreams-ai'));
          $service_img   = $service_id ? get_the_post_thumbnail_url($service_id, 'thumbnail') : '';
          $branch        = '';
          if ($service_id) {
            $terms = wp_get_object_terms($service_id, 'service_branch');
            if (! is_wp_error($terms) && ! empty($terms)) {
              $branch = $terms[0]->name;
            }
          }
          $date_label = $row->date ? date_i18n('d M, Y', strtotime($row->date)) : '';
          $slot_html  = $row->slot ? $format_slot($row->slot) : '';

          $order_status = $get_status($row->order_id, $row->payment_status);
          $badge_txt    = $order_status['text'];
          $badge_class  = $order_status['class'];

          $staff_avatar = $row->staff_id ? get_avatar_url((int) $row->staff_id, ['size' => 48]) : '';
          $modal_target = '#appointment_modal_' . (int) $row->id;
          ?>
          <div class="appointments-itemone">
            <div class="appointments-item appointments-itemone">
              <div class="d-flex align-items-center gap-2 flex-wrap">
                <div class="avatar avatar-lg">
                  <?php
                  $service_gallery = get_post_meta($service_id, 'service_gallery', true);
                  if (! empty($service_gallery) && is_array($service_gallery)) {
                    $first_img_id    = (int) $service_gallery[0];
                    $service_img_src = wp_get_attachment_image_src($first_img_id, 'thumbnail');
                    if ($service_img_src) {
                      ?>
                      <img src="<?php echo esc_url($service_img_src[0]); ?>" alt="service" class="rounded-circle">
                      <?php
                    } elseif ($service_img) {
                      ?>
                      <img src="<?php echo esc_url($service_img); ?>" alt="service" class="rounded-circle">
                      <?php
                    } else {
                      ?>
                      <div class="avatar avatar-lg rounded-circle bg-light"></div>
                      <?php
                    }
                  } else {
                    if ($service_img) {
                      ?>
                      <img src="<?php echo esc_url($service_img); ?>" alt="service" class="rounded-circle">
                      <?php
                    } else {
                      ?>
                      <div class="avatar avatar-lg rounded-circle bg-light"></div>
                      <?php
                    }
                  }
                  ?>
                </div>
                <div>
                  <h6 class="fs-16 fw-semibold mb-1 d-flex align-items-center gap-2 flex-wrap text-capitalize">
                    <a href="<?php echo esc_url($service_id ? get_permalink($service_id) : '#'); ?>">
                      <?php
                      $__st = $service_title;
                      echo esc_html(function_exists('dreamsai_sentencecase') ? dreamsai_sentencecase($__st) : $__st);
                      ?>
                    </a>
                    <span class="badge badge-sm <?php echo esc_attr($badge_class); ?>"><?php echo esc_html($badge_txt); ?></span>
                  </h6>
                  <?php if ($branch) : ?>
                    <p class="mb-0 fs-14 text-capitalize">
                      <i class="ti ti-map-pin-bolt me-1 fs-14"></i>
                      <?php
                      $__br = $branch;
                      echo esc_html(function_exists('dreamsai_sentencecase') ? dreamsai_sentencecase($__br) : $__br);
                      ?>
                    </p>
                  <?php endif; ?>
                </div>
              </div>
              <div>
                <h6 class="mb-1 fs-16 fw-semibold"><?php echo esc_html($date_label); ?></h6>
                <p class="fs-14 mb-0 d-flex align-items-center gap-2"><?php echo wp_kses_post($slot_html); ?></p>
              </div>
              <div>
                <p class="fs-14 mb-1"><?php esc_html_e('Assigned Staff', 'dreams-ai'); ?></p>
                <div class="avatar-list-stacked">
                  <?php if ($staff_avatar) : ?>
                    <span class="avatar avatar-sm rounded-circle border-0">
                      <img src="<?php echo esc_url($staff_avatar); ?>" class="img-fluid rounded-circle border border-white" alt="staff">
                    </span>
                  <?php else : ?>
                    <span class="text-muted"><?php esc_html_e('Not assigned', 'dreams-ai'); ?></span>
                  <?php endif; ?>
                </div>
              </div>
              <div class="text-xl-end">
                <a href="#" class="btn btn-sm border border-color btn-hover d-inline-flex align-items-center fs-13 fw-medium" data-bs-toggle="modal" data-bs-target="<?php echo esc_attr($modal_target); ?>">
                  <?php esc_html_e('View Details', 'dreams-ai'); ?>
                  <i class="ti ti-chevron-right ms-1"></i>
                </a>
              </div>
            </div>
          </div>
          <?php
        }
      }
      $html     = ob_get_clean();
      $has_more = ($total_bookings > $page * $per_page);
      wp_send_json_success(['html' => $html, 'has_more' => $has_more]);
    }

    // Staff dashboard: rows for this staff member
    if ($role === 'staff') {
      $current_user = wp_get_current_user();
      $staff_id     = (int) $current_user->ID;
      $per_page     = 10; // keep in sync with staff bookings template

      $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : '';
      $status = isset($_POST['status']) ? sanitize_key(wp_unslash($_POST['status'])) : '';
      $from   = isset($_POST['from']) ? sanitize_text_field(wp_unslash($_POST['from'])) : '';
      $to     = isset($_POST['to']) ? sanitize_text_field(wp_unslash($_POST['to'])) : '';

      $posts  = $wpdb->posts;
      $where  = [];
      $params = [];

      $staff_col = 'staff_id';
      $cols      = $wpdb->get_col("SHOW COLUMNS FROM {$book_table}", 0);
      if ($cols && is_array($cols) && ! in_array('staff_id', $cols, true)) {
        $candidates = ['employee_id', 'assigned_staff', 'assigned_staff_id', 'provider_id', 'provider_user_id'];
        foreach ($candidates as $cand) {
          if (in_array($cand, $cols, true)) {
            $staff_col = $cand;
            break;
          }
        }
      }
      $where[]  = "b.`$staff_col` = %d";
      $params[] = $staff_id;

      if ($search !== '') {
        $like     = '%' . $wpdb->esc_like($search) . '%';
        $where[]  = '(p.post_title LIKE %s)';
        $params[] = $like;
      }
      if ($status !== '') {
        $where[]  = 'b.payment_status = %s';
        $params[] = $status;
      }
      if ($from !== '') {
        $where[]  = 'b.date >= %s';
        $params[] = $from;
      }
      if ($to !== '') {
        $where[]  = 'b.date <= %s';
        $params[] = $to;
      }

      $where_sql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';

      $count_sql = "SELECT COUNT(*)
                     FROM $book_table b
                     INNER JOIN $posts p ON p.ID = b.service_id
                     $where_sql";
      $total_bookings = (int) $wpdb->get_var($wpdb->prepare($count_sql, $params));

      $offset = ($page - 1) * $per_page;

      $rows_sql = "SELECT b.*, p.post_title
                   FROM $book_table b
                   INNER JOIN $posts p ON p.ID = b.service_id
                   $where_sql
                   ORDER BY b.date DESC, b.slot ASC
                   LIMIT %d OFFSET %d";
      $rows = $wpdb->get_results($wpdb->prepare($rows_sql, array_merge($params, [$per_page, $offset])));

      $format_slot = function ($slot) {
        $slot = trim((string) $slot);
        if ($slot === '' || strpos($slot, '-') === false) {
          return esc_html($slot);
        }
        list($s, $e) = array_map('trim', explode('-', $slot, 2));
        return date_i18n('h:i A', strtotime($s)) . '<span>-</span> ' . date_i18n('h:i A', strtotime($e));
      };

      ob_start();
      if (! empty($rows)) {
        foreach ($rows as $row) {
          $service_id    = (int) $row->service_id;
          $service_title = $row->post_title ?: ($service_id ? get_the_title($service_id) : __('Service', 'dreams-ai'));
          $service_img   = $service_id ? get_the_post_thumbnail_url($service_id, 'thumbnail') : '';
          $branch        = '';
          if ($service_id) {
            $terms = wp_get_object_terms($service_id, 'service_branch');
            if (! is_wp_error($terms) && ! empty($terms)) {
              $branch = $terms[0]->name;
            }
          }
          $date_label = $row->date ? date_i18n('d M, Y', strtotime($row->date)) : '';
          $slot_html  = $row->slot ? $format_slot($row->slot) : '';

          $badge_txt   = 'Scheduled';
          $badge_class = 'badge-soft-info';
          $ps          = strtolower((string) $row->payment_status);
          if ($ps === 'completed') {
            $badge_txt   = 'Completed';
            $badge_class = 'badge-soft-success';
          } elseif ($ps === 'cancelled' || $ps === 'refunded') {
            $badge_txt   = 'Cancelled';
            $badge_class = 'badge-soft-danger';
          }

          $cust_name   = $row->customer_id ? get_the_author_meta('display_name', (int) $row->customer_id) : ($row->customer_name ?: '');
          $modal_target = '#appointment_modal_' . (int) $row->id;
          ?>
          <div class="appointments-itemone">
            <div class="appointments-item appointments-itemone">
              <div class="d-flex align-items-center gap-2 flex-wrap">
                <div class="avatar avatar-lg">
                  <?php
                  $service_gallery = get_post_meta($service_id, 'service_gallery', true);
                  if (! empty($service_gallery) && is_array($service_gallery)) {
                    $first_img_id    = (int) $service_gallery[0];
                    $service_img_src = wp_get_attachment_image_src($first_img_id, 'thumbnail');
                    if ($service_img_src) {
                      ?>
                      <img src="<?php echo esc_url($service_img_src[0]); ?>" alt="service" class="rounded-circle">
                      <?php
                    } elseif ($service_img) {
                      ?>
                      <img src="<?php echo esc_url($service_img); ?>" alt="service" class="rounded-circle">
                      <?php
                    } else {
                      ?>
                      <div class="avatar avatar-lg rounded-circle bg-light"></div>
                      <?php
                    }
                  } else {
                    if ($service_img) {
                      ?>
                      <img src="<?php echo esc_url($service_img); ?>" alt="service" class="rounded-circle">
                      <?php
                    } else {
                      ?>
                      <div class="avatar avatar-lg rounded-circle bg-light"></div>
                      <?php
                    }
                  }
                  ?>
                </div>
                <div>
                  <h6 class="fs-16 fw-semibold mb-1 d-flex align-items-center gap-2 flex-wrap text-capitalize">
                    <a href="<?php echo esc_url($service_id ? get_permalink($service_id) : '#'); ?>">
                      <?php
                      $__st = $service_title;
                      echo esc_html(function_exists('dreamsai_sentencecase') ? dreamsai_sentencecase($__st) : $__st);
                      ?>
                    </a>
                    <span class="badge badge-sm <?php echo esc_attr($badge_class); ?>"><?php echo esc_html($badge_txt); ?></span>
                  </h6>
                  <?php if ($branch) : ?>
                    <p class="mb-0 fs-14 text-capitalize">
                      <i class="ti ti-map-pin-bolt me-1 fs-14"></i>
                      <?php
                      $__br = $branch;
                      echo esc_html(function_exists('dreamsai_sentencecase') ? dreamsai_sentencecase($__br) : $__br);
                      ?>
                    </p>
                  <?php endif; ?>
                </div>
              </div>
              <div>
                <h6 class="mb-1 fs-16 fw-semibold"><?php echo esc_html($date_label); ?></h6>
                <p class="fs-14 mb-0 d-flex align-items-center gap-2"><?php echo wp_kses_post($slot_html); ?></p>
              </div>
              <div>
                <p class="fs-14 mb-1"><?php esc_html_e('Customer', 'dreams-ai'); ?></p>
                <div class="fs-14"><?php echo esc_html($cust_name ?: __('Guest', 'dreams-ai')); ?></div>
              </div>
              <div class="text-xl-end">
                <a href="#" class="btn btn-sm border border-color btn-hover d-inline-flex align-items-center fs-13 fw-medium" data-bs-toggle="modal" data-bs-target="<?php echo esc_attr($modal_target); ?>">
                  <?php esc_html_e('View Details', 'dreams-ai'); ?>
                  <i class="ti ti-chevron-right ms-1"></i>
                </a>
              </div>
            </div>
          </div>
          <?php
        }
      }
      $html     = ob_get_clean();
      $has_more = ($total_bookings > $page * $per_page);
      wp_send_json_success(['html' => $html, 'has_more' => $has_more]);
    }

    wp_send_json_error(['message' => 'Invalid role']);
}

// Toggle favorite
add_action('wp_ajax_pgai_toggle_favorite', 'pgai_toggle_favorite');

function pgai_toggle_favorite() {
    if ( ! is_user_logged_in() ) wp_send_json_error();

    $post_id = intval($_POST['post_id']);
    $user_id = get_current_user_id();

    $favorites = (array) get_user_meta($user_id, 'pgai_favorites', true);

    if (in_array($post_id, $favorites)) {
        $favorites = array_diff($favorites, [$post_id]);
        $status = 'removed';
    } else {
        $favorites[] = $post_id;
        $status = 'added';
    }

    update_user_meta($user_id, 'pgai_favorites', $favorites);

    wp_send_json_success([
        'status' => $status
    ]);
}
 
// Track when a user views a presentation
add_action('wp', function() {
    if (is_singular('presentation') || (is_admin() && isset($_GET['post']) && isset($_GET['action']) && $_GET['action'] == 'edit')) {
        $post_id = is_admin() ? intval($_GET['post']) : get_the_ID();
        $user_id = get_current_user_id();
        
        if ($post_id && $user_id) {
            $viewed_posts = get_user_meta($user_id, 'recently_viewed_presentations', true);
            if (!is_array($viewed_posts)) {
                $viewed_posts = array();
            }
            
            // Add or update timestamp for this post
            $viewed_posts[$post_id] = current_time('timestamp');
            
            // Keep only last 50 viewed items
            if (count($viewed_posts) > 50) {
                $viewed_posts = array_slice($viewed_posts, -50, 50, true);
            }
            
            update_user_meta($user_id, 'recently_viewed_presentations', $viewed_posts);
        }
    }
});

// Function to get user's recently viewed posts
function get_user_recently_viewed_presentations($user_id = null) {
    if (!$user_id) {
        $user_id = get_current_user_id();
    }
    
    $viewed_posts = get_user_meta($user_id, 'recently_viewed_presentations', true);
    return is_array($viewed_posts) ? $viewed_posts : array();
}

add_action('wp_enqueue_scripts', function () {

    wp_enqueue_style(
        'dreams-ai-frontend',
        plugins_url('assets/admin.css', dirname(__FILE__)),
        [],
        time()
    );

});

 
function dreams_ai_enqueue_icons_and_tailwind() {

    // Font Awesome 6.4
    wp_enqueue_style(
        'font-awesome-6',
        'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css',
        [],
        '6.4.0'
    );

    // Tailwind CDN
    wp_enqueue_script(
        'tailwind-cdn',
        'https://cdn.tailwindcss.com',
        [],
        null,
        false // load in header
    );
}
add_action('wp_enqueue_scripts', 'dreams_ai_enqueue_icons_and_tailwind');

/**
 * Enqueue Bootstrap for plugin frontend
 */
add_action('wp_enqueue_scripts', 'dreams_ai_enqueue_bootstrap');
function dreams_ai_enqueue_bootstrap() {

    // Bootstrap CSS
    wp_enqueue_style(
        'bootstrap-5',
        'https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css',
        [],
        '5.3.2'
    );

    // Bootstrap JS (bundle includes Popper)
    wp_enqueue_script(
        'bootstrap-5',
        'https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js',
        [],
        '5.3.2',
        true
    );
}