<?php

if (!defined('ABSPATH')) {
    exit;
}

// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only admin filters rely on query parameters.

function chbk_register_admin_pages()
{
    add_action('admin_menu', 'chbk_admin_menu');
    add_action('admin_init', 'chbk_redirect_builtin_cpt_tables');
    add_action('admin_notices', 'chbk_render_trip_overlap_notice');
    add_action('admin_notices', 'chbk_render_trip_clone_notice');
    add_action('save_post_chbk_trip', 'chbk_validate_trip_overlaps_on_save', 100, 3);
    add_filter('post_row_actions', 'chbk_add_trip_clone_row_action', 10, 2);
    add_action('admin_action_chbk_clone_trip', 'chbk_handle_trip_clone_action');
    add_action('admin_post_chbk_resend_booking_email', 'chbk_handle_resend_booking_email');
    add_action('admin_post_chbk_record_booking_payment', 'chbk_handle_record_booking_payment');
    add_action('admin_post_chbk_update_booking_payment', 'chbk_handle_update_booking_payment');
    add_action('admin_post_chbk_delete_booking_payment', 'chbk_handle_delete_booking_payment');
    add_action('wp_ajax_chbk_update_booking_status', 'chbk_handle_ajax_update_booking_status');
}

function chbk_admin_menu()
{
    $menu_icon = CHBK_PLUGIN_URL . 'assets/img/cb.svg';
    add_menu_page('Charter Booker', 'Charter Booker', 'manage_options', 'chbk_dashboard', 'chbk_render_dashboard_page', $menu_icon, 56);

    add_submenu_page('chbk_dashboard', 'Captains', 'Captains', 'manage_options', 'chbk_captains', 'chbk_render_captains_page');
    add_submenu_page('chbk_dashboard', 'Boats', 'Boats', 'manage_options', 'chbk_boats', 'chbk_render_boats_page');
    add_submenu_page('chbk_dashboard', 'Trips', 'Trips', 'manage_options', 'chbk_trips', 'chbk_render_trips_page');
    add_submenu_page('chbk_dashboard', 'Bookings', 'Bookings', 'manage_options', 'chbk_bookings', 'chbk_render_bookings_page');
    add_submenu_page('chbk_dashboard', 'Payments', 'Payments', 'manage_options', 'chbk_payments', 'chbk_render_payments_page');
    add_submenu_page('chbk_dashboard', 'Settings', 'Settings', 'manage_options', 'chbk_settings', 'chbk_render_settings_page');
    add_submenu_page(null, 'Emails', 'Emails', 'manage_options', 'chbk_emails', 'chbk_render_emails_page');
}

function chbk_redirect_builtin_cpt_tables()
{
    if (!is_admin() || !current_user_can('manage_options')) {
        return;
    }

    global $pagenow;
    if ($pagenow !== 'edit.php') {
        return;
    }

    $post_type = isset($_GET['post_type']) ? sanitize_key(wp_unslash($_GET['post_type'])) : '';
    $targets = [
        'chbk_captain' => 'chbk_captains',
        'chbk_boat' => 'chbk_boats',
        'chbk_trip' => 'chbk_trips',
    ];
    if (!isset($targets[$post_type])) {
        return;
    }

    wp_safe_redirect(add_query_arg(['page' => $targets[$post_type]], admin_url('admin.php')));
    exit;
}

function chbk_dashboard_format_slot_label($trip_id, $trip_datetime_utc)
{
    $trip_datetime_utc = trim((string) $trip_datetime_utc);
    if ($trip_datetime_utc === '') {
        return 'Date not set';
    }

    $tz = $trip_id > 0 && function_exists('chbk_get_trip_timezone') ? chbk_get_trip_timezone($trip_id) : chbk_get_default_site_timezone();
    $local = chbk_from_utc_datetime($trip_datetime_utc, $tz);
    if ($local === '') {
        return 'Date not set';
    }

    try {
        $dt = new DateTime($local, new DateTimeZone($tz));
        return $dt->format('M j, Y g:i a');
    } catch (Exception $e) {
        return $local;
    }
}

function chbk_dashboard_collect_data()
{
    $bookings = get_posts([
        'post_type' => 'chbk_booking',
        'numberposts' => 500,
        'post_status' => 'publish',
    ]);

    $pending = [];
    $confirmed_timeline = [];
    $captain_counts = [];
    $boat_counts = [];
    $now = current_time('timestamp');
    $upcoming_limit_ts = strtotime('+45 days', $now);
    $party_total = 0.0;
    $capacity_total = 0.0;
    $total_bookings = 0;
    $pending_count = 0;
    $confirmed_count = 0;
    $revenue_paid = 0.0;
    $unpaid_total = 0.0;

    foreach ($bookings as $booking) {
        $booking_id = (int) $booking->ID;
        $total_bookings++;
        $trip_id = (int) get_post_meta($booking_id, 'chbk_trip_id', true);
        $trip_title = $trip_id > 0 ? get_the_title($trip_id) : '';
        if (!is_string($trip_title) || trim($trip_title) === '') {
            $trip_title = $booking->post_title;
        }

        $trip_datetime_utc = (string) get_post_meta($booking_id, 'chbk_trip_datetime_utc', true);
        $slot_label = chbk_dashboard_format_slot_label($trip_id, $trip_datetime_utc);
        $slot_ts = $trip_datetime_utc !== '' ? strtotime($trip_datetime_utc . ' UTC') : false;
        $customer_name = (string) get_post_meta($booking_id, 'chbk_customer_name', true);
        $customer_email = (string) get_post_meta($booking_id, 'chbk_customer_email', true);
        $party_size = max(0, (int) get_post_meta($booking_id, 'chbk_party_size', true));
        $trip_capacity = $trip_id > 0 ? max(0, (int) chbk_get_trip_capacity($trip_id)) : 0;

        if ($trip_capacity > 0) {
            $party_total += (float) $party_size;
            $capacity_total += (float) $trip_capacity;
        }

        $captain_id = (int) get_post_meta($booking_id, 'chbk_captain_id', true);
        $captain_label = $captain_id > 0 ? get_the_title($captain_id) : '';
        if (!is_string($captain_label) || trim($captain_label) === '') {
            $captain_label = 'Unassigned';
        }
        if (!isset($captain_counts[$captain_label])) {
            $captain_counts[$captain_label] = 0;
        }
        $captain_counts[$captain_label]++;

        $boat_id = (int) get_post_meta($booking_id, 'chbk_boat_id', true);
        $boat_label = $boat_id > 0 ? get_the_title($boat_id) : '';
        if (!is_string($boat_label) || trim($boat_label) === '') {
            $boat_label = 'Unassigned';
        }
        if (!isset($boat_counts[$boat_label])) {
            $boat_counts[$boat_label] = 0;
        }
        $boat_counts[$boat_label]++;

        $booking_status = (string) get_post_meta($booking_id, 'chbk_booking_status', true);
        if ($booking_status === 'pending') {
            $pending_count++;
            $pending[] = [
                'id' => $booking_id,
                'trip_title' => $trip_title,
                'slot_label' => $slot_label,
                'customer_label' => trim($customer_name) !== '' ? $customer_name : $customer_email,
            ];
        }

        if (($booking_status === 'confirmed' || $booking_status === 'completed') && $slot_ts && $slot_ts >= $now && $slot_ts <= $upcoming_limit_ts) {
            $confirmed_count++;
            $confirmed_timeline[] = [
                'id' => $booking_id,
                'trip_title' => $trip_title,
                'slot_label' => $slot_label,
                'slot_ts' => (int) $slot_ts,
                'captain_label' => $captain_label,
                'boat_label' => $boat_label,
            ];
        } elseif ($booking_status === 'confirmed' || $booking_status === 'completed') {
            $confirmed_count++;
        }

        $total_price = (float) get_post_meta($booking_id, 'chbk_total_price', true);
        $paid_amount = function_exists('chbk_get_booking_paid_amount') ? (float) chbk_get_booking_paid_amount($booking_id) : 0.0;
        $revenue_paid += $paid_amount;
        if ($total_price > $paid_amount) {
            $unpaid_total += ($total_price - $paid_amount);
        }

    }

    usort($pending, function ($a, $b) {
        return ($a['id'] ?? 0) <=> ($b['id'] ?? 0);
    });
    usort($confirmed_timeline, function ($a, $b) {
        return ($a['slot_ts'] ?? 0) <=> ($b['slot_ts'] ?? 0);
    });
    arsort($captain_counts);
    arsort($boat_counts);

    $fill_rate = $capacity_total > 0 ? min(100, round(($party_total / $capacity_total) * 100, 1)) : 0;

    $data = [
        'total_bookings' => $total_bookings,
        'pending_count' => $pending_count,
        'confirmed_count' => $confirmed_count,
        'revenue_paid' => round($revenue_paid, 2),
        'unpaid_total' => round($unpaid_total, 2),
        'pending' => array_slice($pending, 0, 8),
        'confirmed_timeline' => array_slice($confirmed_timeline, 0, 10),
        'captain_counts' => array_slice($captain_counts, 0, 8, true),
        'boat_counts' => array_slice($boat_counts, 0, 8, true),
        'fill_rate' => $fill_rate,
    ];
    return apply_filters('chbk_dashboard_data', $data);
}

function chbk_render_dashboard_page()
{
    $data = chbk_dashboard_collect_data();
    $currency = chbk_get_setting('currency', 'USD');

    echo '<div class="wrap chbk-dashboard-wrap"><h1>Charter Booker Dashboard</h1>';
    echo '<div class="chbk-dashboard-grid">';
    echo '<section class="chbk-dash-panel chbk-dash-kpis">';
    echo '<div class="chbk-dash-kpi"><span class="chbk-dash-kpi-label">Total Bookings</span><strong>' . esc_html((string) $data['total_bookings']) . '</strong></div>';
    echo '<div class="chbk-dash-kpi"><span class="chbk-dash-kpi-label">Pending</span><strong>' . esc_html((string) $data['pending_count']) . '</strong></div>';
    echo '<div class="chbk-dash-kpi"><span class="chbk-dash-kpi-label">Confirmed</span><strong>' . esc_html((string) $data['confirmed_count']) . '</strong></div>';
    echo '<div class="chbk-dash-kpi"><span class="chbk-dash-kpi-label">Collected</span><strong>' . esc_html(number_format((float) $data['revenue_paid'], 2)) . ' ' . esc_html($currency) . '</strong></div>';
    echo '</section>';

    echo '<section class="chbk-dash-panel chbk-dash-panel-pending">';
    echo '<h2>Pending Bookings</h2>';
    if (empty($data['pending'])) {
        echo '<p class="description">No pending bookings right now.</p>';
    } else {
        echo '<ul class="chbk-dash-list">';
        foreach ($data['pending'] as $row) {
            echo '<li>';
            echo '<a href="' . esc_url(get_edit_post_link((int) $row['id'])) . '">' . esc_html((string) $row['trip_title']) . '</a>';
            echo '<span>' . esc_html((string) $row['slot_label']) . '</span>';
            if (!empty($row['customer_label'])) {
                echo '<span class="description">' . esc_html((string) $row['customer_label']) . '</span>';
            }
            echo '</li>';
        }
        echo '</ul>';
    }
    echo '</section>';

    echo '<section class="chbk-dash-panel chbk-dash-panel-timeline">';
    echo '<h2>Confirmed Timeline (Next 45 Days)</h2>';
    if (empty($data['confirmed_timeline'])) {
        echo '<p class="description">No confirmed departures in the next 45 days.</p>';
    } else {
        echo '<div class="chbk-dash-timeline">';
        foreach ($data['confirmed_timeline'] as $row) {
            echo '<article class="chbk-dash-timeline-item">';
            echo '<h3><a href="' . esc_url(get_edit_post_link((int) $row['id'])) . '">' . esc_html((string) $row['trip_title']) . '</a></h3>';
            echo '<p>' . esc_html((string) $row['slot_label']) . '</p>';
            echo '<p class="description">' . esc_html((string) $row['captain_label']) . ' · ' . esc_html((string) $row['boat_label']) . '</p>';
            echo '</article>';
        }
        echo '</div>';
    }
    echo '</section>';

    echo '<section class="chbk-dash-panel chbk-dash-panel-assignment">';
    echo '<h2>Bookings by Captain</h2>';
    if (empty($data['captain_counts'])) {
        echo '<p class="description">No captain assignments yet.</p>';
    } else {
        echo '<div class="chbk-dash-mini-cards">';
        foreach ($data['captain_counts'] as $label => $count) {
            echo '<div class="chbk-dash-mini-card"><span>' . esc_html((string) $label) . '</span><strong>' . esc_html((string) $count) . '</strong></div>';
        }
        echo '</div>';
    }
    echo '</section>';

    echo '<section class="chbk-dash-panel chbk-dash-panel-assignment">';
    echo '<h2>Bookings by Boat</h2>';
    if (empty($data['boat_counts'])) {
        echo '<p class="description">No boat assignments yet.</p>';
    } else {
        echo '<div class="chbk-dash-mini-cards">';
        foreach ($data['boat_counts'] as $label => $count) {
            echo '<div class="chbk-dash-mini-card"><span>' . esc_html((string) $label) . '</span><strong>' . esc_html((string) $count) . '</strong></div>';
        }
        echo '</div>';
    }
    echo '</section>';

    do_action('chbk_render_dashboard_monthly_panels', $data, $currency);

    echo '</div>';
    echo '</div>';
}

function chbk_admin_title_matches_search($title, $search)
{
    $title = trim((string) $title);
    $search = trim((string) $search);
    if ($search === '') {
        return true;
    }

    return stripos($title, $search) !== false;
}

function chbk_render_captains_page()
{
    $search = isset($_GET['chbk_search']) ? sanitize_text_field(wp_unslash($_GET['chbk_search'])) : '';
    $status_filter = isset($_GET['chbk_status']) ? sanitize_key(wp_unslash($_GET['chbk_status'])) : '';
    if (!in_array($status_filter, ['', 'active', 'inactive'], true)) {
        $status_filter = '';
    }

    $captains = get_posts([
        'post_type' => 'chbk_captain',
        'numberposts' => 300,
        'post_status' => ['publish', 'draft', 'pending', 'private', 'future'],
        'orderby' => 'title',
        'order' => 'ASC',
    ]);
    $trips = get_posts([
        'post_type' => 'chbk_trip',
        'numberposts' => -1,
        'post_status' => ['publish', 'draft', 'pending', 'private', 'future'],
    ]);

    $trip_counts = [];
    foreach ($trips as $trip) {
        $captain_id = (int) get_post_meta($trip->ID, 'chbk_captain_id', true);
        if ($captain_id > 0) {
            $trip_counts[$captain_id] = isset($trip_counts[$captain_id]) ? ($trip_counts[$captain_id] + 1) : 1;
        }
    }

    $add_new_url = admin_url('post-new.php?post_type=chbk_captain');
    $reset_url = add_query_arg(['page' => 'chbk_captains'], admin_url('admin.php'));

    echo '<div class="wrap"><h1 class="wp-heading-inline">Captains</h1>';
    echo '<a href="' . esc_url($add_new_url) . '" class="page-title-action">Add New</a>';
    echo '<hr class="wp-header-end">';

    echo '<form method="get" style="margin:1rem 0;display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;">';
    echo '<input type="hidden" name="page" value="chbk_captains">';
    echo '<p style="margin:0;"><label>Search<br><input type="text" name="chbk_search" value="' . esc_attr($search) . '" placeholder="Captain name"></label></p>';
    echo '<p style="margin:0;"><label>Status<br><select name="chbk_status">';
    echo '<option value="">All Statuses</option>';
    echo '<option value="active" ' . selected($status_filter, 'active', false) . '>Active</option>';
    echo '<option value="inactive" ' . selected($status_filter, 'inactive', false) . '>Inactive</option>';
    echo '</select></label></p>';
    echo '<p style="margin:0;"><button type="submit" class="button button-primary">Filter</button> <a class="button" href="' . esc_url($reset_url) . '">Reset</a></p>';
    echo '</form>';

    echo '<table class="widefat striped"><thead><tr><th>Captain</th><th>User</th><th>Status</th><th>Trips</th><th>Updated</th><th>Actions</th></tr></thead><tbody>';
    $rendered = 0;
    foreach ($captains as $captain) {
        $title = (string) $captain->post_title;
        if (!chbk_admin_title_matches_search($title, $search)) {
            continue;
        }

        $status = (string) get_post_meta($captain->ID, 'chbk_status', true);
        if (!in_array($status, ['active', 'inactive'], true)) {
            $status = 'active';
        }
        if ($status_filter !== '' && $status !== $status_filter) {
            continue;
        }

        $user_id = (int) get_post_meta($captain->ID, 'chbk_user_id', true);
        $user_label = 'N/A';
        if ($user_id > 0) {
            $user = get_userdata($user_id);
            if ($user) {
                $user_label = (string) $user->display_name;
            }
        }

        $edit_url = get_edit_post_link($captain->ID);
        $trips_url = add_query_arg(['page' => 'chbk_trips', 'chbk_captain_id' => (int) $captain->ID], admin_url('admin.php'));
        $actions = '<a href="' . esc_url($edit_url) . '">Edit</a> | <a href="' . esc_url($trips_url) . '">View Trips</a>';
        $status_label = $status === 'inactive' ? 'Inactive' : 'Active';
        $updated = get_the_modified_date('M j, Y g:i a', $captain);

        printf(
            '<tr><td>%s</td><td>%s</td><td>%s</td><td>%d</td><td>%s</td><td>%s</td></tr>',
            esc_html($title !== '' ? $title : ('Captain #' . (int) $captain->ID)),
            esc_html($user_label),
            esc_html($status_label),
            (int) (isset($trip_counts[$captain->ID]) ? $trip_counts[$captain->ID] : 0),
            esc_html((string) $updated),
            wp_kses_post($actions)
        );
        $rendered++;
    }

    if ($rendered === 0) {
        echo '<tr><td colspan="6">No captains found.</td></tr>';
    }
    echo '</tbody></table>';
    echo '</div>';
}

function chbk_render_boats_page()
{
    $search = isset($_GET['chbk_search']) ? sanitize_text_field(wp_unslash($_GET['chbk_search'])) : '';
    $status_filter = isset($_GET['chbk_status']) ? sanitize_key(wp_unslash($_GET['chbk_status'])) : '';
    if (!in_array($status_filter, ['', 'active', 'inactive'], true)) {
        $status_filter = '';
    }

    $boats = get_posts([
        'post_type' => 'chbk_boat',
        'numberposts' => 300,
        'post_status' => ['publish', 'draft', 'pending', 'private', 'future'],
        'orderby' => 'title',
        'order' => 'ASC',
    ]);
    $trips = get_posts([
        'post_type' => 'chbk_trip',
        'numberposts' => -1,
        'post_status' => ['publish', 'draft', 'pending', 'private', 'future'],
    ]);

    $trip_counts = [];
    foreach ($trips as $trip) {
        $boat_id = (int) get_post_meta($trip->ID, 'chbk_boat_id', true);
        if ($boat_id > 0) {
            $trip_counts[$boat_id] = isset($trip_counts[$boat_id]) ? ($trip_counts[$boat_id] + 1) : 1;
        }
    }

    $add_new_url = admin_url('post-new.php?post_type=chbk_boat');
    $reset_url = add_query_arg(['page' => 'chbk_boats'], admin_url('admin.php'));

    echo '<div class="wrap"><h1 class="wp-heading-inline">Boats</h1>';
    echo '<a href="' . esc_url($add_new_url) . '" class="page-title-action">Add New</a>';
    echo '<hr class="wp-header-end">';

    echo '<form method="get" style="margin:1rem 0;display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;">';
    echo '<input type="hidden" name="page" value="chbk_boats">';
    echo '<p style="margin:0;"><label>Search<br><input type="text" name="chbk_search" value="' . esc_attr($search) . '" placeholder="Boat name"></label></p>';
    echo '<p style="margin:0;"><label>Status<br><select name="chbk_status">';
    echo '<option value="">All Statuses</option>';
    echo '<option value="active" ' . selected($status_filter, 'active', false) . '>Active</option>';
    echo '<option value="inactive" ' . selected($status_filter, 'inactive', false) . '>Inactive</option>';
    echo '</select></label></p>';
    echo '<p style="margin:0;"><button type="submit" class="button button-primary">Filter</button> <a class="button" href="' . esc_url($reset_url) . '">Reset</a></p>';
    echo '</form>';

    echo '<table class="widefat striped"><thead><tr><th>Boat</th><th>Status</th><th>Capacity</th><th>Overbooking</th><th>Trips</th><th>Updated</th><th>Actions</th></tr></thead><tbody>';
    $rendered = 0;
    foreach ($boats as $boat) {
        $title = (string) $boat->post_title;
        if (!chbk_admin_title_matches_search($title, $search)) {
            continue;
        }

        $status = (string) get_post_meta($boat->ID, 'chbk_status', true);
        if (!in_array($status, ['active', 'inactive'], true)) {
            $status = 'active';
        }
        if ($status_filter !== '' && $status !== $status_filter) {
            continue;
        }

        $min_capacity = (int) get_post_meta($boat->ID, 'chbk_min_capacity', true);
        $max_capacity = (int) get_post_meta($boat->ID, 'chbk_max_capacity', true);
        $overbooking = (int) get_post_meta($boat->ID, 'chbk_allows_overbooking', true) === 1 ? 'Yes' : 'No';
        $edit_url = get_edit_post_link($boat->ID);
        $trips_url = add_query_arg(['page' => 'chbk_trips', 'chbk_boat_id' => (int) $boat->ID], admin_url('admin.php'));
        $actions = '<a href="' . esc_url($edit_url) . '">Edit</a> | <a href="' . esc_url($trips_url) . '">View Trips</a>';
        $status_label = $status === 'inactive' ? 'Inactive' : 'Active';
        $updated = get_the_modified_date('M j, Y g:i a', $boat);
        $capacity_label = max(0, $min_capacity) . ' - ' . max(0, $max_capacity);

        printf(
            '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%d</td><td>%s</td><td>%s</td></tr>',
            esc_html($title !== '' ? $title : ('Boat #' . (int) $boat->ID)),
            esc_html($status_label),
            esc_html($capacity_label),
            esc_html($overbooking),
            (int) (isset($trip_counts[$boat->ID]) ? $trip_counts[$boat->ID] : 0),
            esc_html((string) $updated),
            wp_kses_post($actions)
        );
        $rendered++;
    }

    if ($rendered === 0) {
        echo '<tr><td colspan="7">No boats found.</td></tr>';
    }
    echo '</tbody></table>';
    echo '</div>';
}

function chbk_render_trips_page()
{
    $search = isset($_GET['chbk_search']) ? sanitize_text_field(wp_unslash($_GET['chbk_search'])) : '';
    $captain_filter = isset($_GET['chbk_captain_id']) ? absint($_GET['chbk_captain_id']) : 0;
    $boat_filter = isset($_GET['chbk_boat_id']) ? absint($_GET['chbk_boat_id']) : 0;
    $status_filter = isset($_GET['chbk_status']) ? sanitize_key(wp_unslash($_GET['chbk_status'])) : '';
    $bookable_filter = isset($_GET['chbk_bookable']) ? sanitize_key(wp_unslash($_GET['chbk_bookable'])) : '';
    if (!in_array($status_filter, ['', 'active', 'inactive'], true)) {
        $status_filter = '';
    }
    if (!in_array($bookable_filter, ['', 'yes', 'no'], true)) {
        $bookable_filter = '';
    }

    $captains = get_posts([
        'post_type' => 'chbk_captain',
        'post_status' => 'publish',
        'numberposts' => -1,
        'orderby' => 'title',
        'order' => 'ASC',
    ]);
    $boats = get_posts([
        'post_type' => 'chbk_boat',
        'post_status' => 'publish',
        'numberposts' => -1,
        'orderby' => 'title',
        'order' => 'ASC',
    ]);
    $trips = get_posts([
        'post_type' => 'chbk_trip',
        'numberposts' => 300,
        'post_status' => ['publish', 'draft', 'pending', 'private', 'future'],
        'orderby' => 'title',
        'order' => 'ASC',
    ]);

    $add_new_url = admin_url('post-new.php?post_type=chbk_trip');
    $reset_url = add_query_arg(['page' => 'chbk_trips'], admin_url('admin.php'));

    echo '<div class="wrap"><h1 class="wp-heading-inline">Trips</h1>';
    echo '<a href="' . esc_url($add_new_url) . '" class="page-title-action">Add New</a>';
    echo '<hr class="wp-header-end">';

    echo '<form method="get" style="margin:1rem 0;display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;">';
    echo '<input type="hidden" name="page" value="chbk_trips">';
    echo '<p style="margin:0;"><label>Search<br><input type="text" name="chbk_search" value="' . esc_attr($search) . '" placeholder="Trip title"></label></p>';
    echo '<p style="margin:0;"><label>Captain<br><select name="chbk_captain_id"><option value="">All Captains</option>';
    foreach ($captains as $captain) {
        printf('<option value="%d" %s>%s</option>', (int) $captain->ID, selected($captain_filter, (int) $captain->ID, false), esc_html((string) $captain->post_title));
    }
    echo '</select></label></p>';
    echo '<p style="margin:0;"><label>Boat<br><select name="chbk_boat_id"><option value="">All Boats</option>';
    foreach ($boats as $boat) {
        printf('<option value="%d" %s>%s</option>', (int) $boat->ID, selected($boat_filter, (int) $boat->ID, false), esc_html((string) $boat->post_title));
    }
    echo '</select></label></p>';
    echo '<p style="margin:0;"><label>Status<br><select name="chbk_status">';
    echo '<option value="">All Statuses</option>';
    echo '<option value="active" ' . selected($status_filter, 'active', false) . '>Active</option>';
    echo '<option value="inactive" ' . selected($status_filter, 'inactive', false) . '>Inactive</option>';
    echo '</select></label></p>';
    echo '<p style="margin:0;"><label>Bookable<br><select name="chbk_bookable">';
    echo '<option value="">All</option>';
    echo '<option value="yes" ' . selected($bookable_filter, 'yes', false) . '>Yes</option>';
    echo '<option value="no" ' . selected($bookable_filter, 'no', false) . '>No</option>';
    echo '</select></label></p>';
    echo '<p style="margin:0;"><button type="submit" class="button button-primary">Filter</button> <a class="button" href="' . esc_url($reset_url) . '">Reset</a></p>';
    echo '</form>';

    echo '<table class="widefat striped"><thead><tr><th>Trip</th><th>Captain</th><th>Boat</th><th>Status</th><th>Start Time</th><th>Duration</th><th>Bookable</th><th>Updated</th><th>Actions</th></tr></thead><tbody>';
    $rendered = 0;
    foreach ($trips as $trip) {
        $title = (string) $trip->post_title;
        if (!chbk_admin_title_matches_search($title, $search)) {
            continue;
        }

        $trip_status = (string) get_post_meta($trip->ID, 'chbk_active_status', true);
        if (!in_array($trip_status, ['active', 'inactive'], true)) {
            $trip_status = 'active';
        }
        if ($status_filter !== '' && $trip_status !== $status_filter) {
            continue;
        }

        $captain_id = (int) get_post_meta($trip->ID, 'chbk_captain_id', true);
        $boat_id = (int) get_post_meta($trip->ID, 'chbk_boat_id', true);
        if ($captain_filter > 0 && $captain_filter !== $captain_id) {
            continue;
        }
        if ($boat_filter > 0 && $boat_filter !== $boat_id) {
            continue;
        }

        $bookable = function_exists('chbk_is_trip_bookable') ? chbk_is_trip_bookable((int) $trip->ID) : true;
        $bookable_label = $bookable ? 'Yes' : 'No';
        if ($bookable_filter === 'yes' && !$bookable) {
            continue;
        }
        if ($bookable_filter === 'no' && $bookable) {
            continue;
        }

        $captain_label = $captain_id > 0 ? get_the_title($captain_id) : 'Unassigned';
        if (!is_string($captain_label) || trim($captain_label) === '') {
            $captain_label = 'Unassigned';
        }
        $boat_label = $boat_id > 0 ? get_the_title($boat_id) : 'Unassigned';
        if (!is_string($boat_label) || trim($boat_label) === '') {
            $boat_label = 'Unassigned';
        }

        $start_time = function_exists('chbk_get_trip_start_time') ? chbk_get_trip_start_time((int) $trip->ID) : '';
        $duration = function_exists('chbk_get_trip_duration_minutes') ? chbk_get_trip_duration_minutes((int) $trip->ID) : 0;
        $duration_label = (int) $duration . ' min';
        $status_label = $trip_status === 'inactive' ? 'Inactive' : 'Active';
        $updated = get_the_modified_date('M j, Y g:i a', $trip);

        $clone_url = wp_nonce_url(
            admin_url('admin.php?action=chbk_clone_trip&post=' . (int) $trip->ID),
            'chbk_clone_trip_' . (int) $trip->ID
        );
        $actions = '<a href="' . esc_url(get_edit_post_link($trip->ID)) . '">Edit</a>';
        $actions .= ' | <a href="' . esc_url($clone_url) . '">Clone</a>';
        $permalink = get_permalink($trip->ID);
        if (is_string($permalink) && $permalink !== '') {
            $actions .= ' | <a href="' . esc_url($permalink) . '" target="_blank" rel="noopener noreferrer">View</a>';
        }

        printf(
            '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>',
            esc_html($title !== '' ? $title : ('Trip #' . (int) $trip->ID)),
            esc_html($captain_label),
            esc_html($boat_label),
            esc_html($status_label),
            esc_html((string) $start_time),
            esc_html($duration_label),
            esc_html($bookable_label),
            esc_html((string) $updated),
            wp_kses_post($actions)
        );
        $rendered++;
    }

    if ($rendered === 0) {
        echo '<tr><td colspan="9">No trips found.</td></tr>';
    }
    echo '</tbody></table>';
    echo '</div>';
}

function chbk_render_bookings_page()
{
    $search = isset($_GET['chbk_search']) ? sanitize_text_field(wp_unslash($_GET['chbk_search'])) : '';
    $captain_filter = isset($_GET['chbk_captain_id']) ? absint($_GET['chbk_captain_id']) : 0;
    $boat_filter = isset($_GET['chbk_boat_id']) ? absint($_GET['chbk_boat_id']) : 0;
    $booking_status_filter = isset($_GET['chbk_booking_status']) ? sanitize_key(wp_unslash($_GET['chbk_booking_status'])) : '';
    $payment_status_filter = isset($_GET['chbk_payment_status']) ? sanitize_key(wp_unslash($_GET['chbk_payment_status'])) : '';
    $slot_start = isset($_GET['chbk_slot_start']) ? sanitize_text_field(wp_unslash($_GET['chbk_slot_start'])) : '';
    $slot_end = isset($_GET['chbk_slot_end']) ? sanitize_text_field(wp_unslash($_GET['chbk_slot_end'])) : '';

    $captains = get_posts([
        'post_type' => 'chbk_captain',
        'post_status' => 'publish',
        'numberposts' => -1,
        'orderby' => 'title',
        'order' => 'ASC',
    ]);
    $boats = get_posts([
        'post_type' => 'chbk_boat',
        'post_status' => 'publish',
        'numberposts' => -1,
        'orderby' => 'title',
        'order' => 'ASC',
    ]);

    $booking_status_options = function_exists('chbk_booking_status_options')
        ? chbk_booking_status_options($booking_status_filter, 'booking')
        : [
            'pending' => 'Pending',
            'confirmed' => 'Confirmed',
            'cancelled' => 'Cancelled',
            'completed' => 'Completed',
            'no_show' => 'No Show',
        ];
    $payment_status_options = function_exists('chbk_booking_status_options')
        ? chbk_booking_status_options($payment_status_filter, 'payment')
        : [
            'pending_payment' => 'Pending Payment',
            'partial' => 'Partial',
            'paid' => 'Paid',
            'refunded' => 'Refunded',
            'failed' => 'Failed',
        ];

    $meta_query = [
        'relation' => 'AND',
    ];

    if ($search !== '') {
        $meta_query[] = [
            'relation' => 'OR',
            [
                'key' => 'chbk_customer_name',
                'value' => $search,
                'compare' => 'LIKE',
            ],
            [
                'key' => 'chbk_customer_email',
                'value' => $search,
                'compare' => 'LIKE',
            ],
        ];
    }

    if ($captain_filter > 0) {
        $meta_query[] = [
            'key' => 'chbk_captain_id',
            'value' => (string) $captain_filter,
            'compare' => '=',
        ];
    }

    if ($boat_filter > 0) {
        $meta_query[] = [
            'key' => 'chbk_boat_id',
            'value' => (string) $boat_filter,
            'compare' => '=',
        ];
    }

    if ($booking_status_filter !== '') {
        $meta_query[] = [
            'key' => 'chbk_booking_status',
            'value' => $booking_status_filter,
            'compare' => '=',
        ];
    }

    if ($payment_status_filter !== '') {
        $meta_query[] = [
            'key' => 'chbk_payment_status',
            'value' => $payment_status_filter,
            'compare' => '=',
        ];
    }

    $has_start = preg_match('/^\d{4}-\d{2}-\d{2}$/', $slot_start) === 1;
    $has_end = preg_match('/^\d{4}-\d{2}-\d{2}$/', $slot_end) === 1;
    if ($has_start || $has_end) {
        if ($has_start && $has_end) {
            $meta_query[] = [
                'key' => 'chbk_trip_datetime_utc',
                'value' => [$slot_start . ' 00:00:00', $slot_end . ' 23:59:59'],
                'compare' => 'BETWEEN',
                'type' => 'DATETIME',
            ];
        } elseif ($has_start) {
            $meta_query[] = [
                'key' => 'chbk_trip_datetime_utc',
                'value' => $slot_start . ' 00:00:00',
                'compare' => '>=',
                'type' => 'DATETIME',
            ];
        } else {
            $meta_query[] = [
                'key' => 'chbk_trip_datetime_utc',
                'value' => $slot_end . ' 23:59:59',
                'compare' => '<=',
                'type' => 'DATETIME',
            ];
        }
    }

    $query_args = [
        'post_type' => 'chbk_booking',
        'numberposts' => 200,
        'post_status' => 'publish',
    ];
    if (count($meta_query) > 1) {
        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Admin filter query requires meta filtering.
        $query_args['meta_query'] = $meta_query;
    }
    $bookings = get_posts($query_args);

    echo '<div class="wrap"><h1>Bookings</h1>';
    if (isset($_GET['chbk_booking_notice'])) {
        $notice = sanitize_key(wp_unslash($_GET['chbk_booking_notice']));
        if ($notice === 'resent') {
            echo '<div class="notice notice-success is-dismissible"><p>Booking confirmation email resent.</p></div>';
        } elseif ($notice === 'resent_failed') {
            echo '<div class="notice notice-error is-dismissible"><p>Unable to resend booking confirmation email.</p></div>';
        } elseif ($notice === 'payment_recorded') {
            echo '<div class="notice notice-success is-dismissible"><p>Payment recorded.</p></div>';
        } elseif ($notice === 'payment_failed') {
            echo '<div class="notice notice-error is-dismissible"><p>Unable to record payment.</p></div>';
        }
    }
    $reset_url = add_query_arg(['page' => 'chbk_bookings'], admin_url('admin.php'));
    echo '<form method="get" style="margin:1rem 0;display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;">';
    echo '<input type="hidden" name="page" value="chbk_bookings">';
    echo '<p style="margin:0;"><label>Search<br><input type="text" name="chbk_search" value="' . esc_attr($search) . '" placeholder="Customer name or email"></label></p>';
    echo '<p style="margin:0;"><label>Captain<br><select name="chbk_captain_id"><option value="">All Captains</option>';
    foreach ($captains as $captain) {
        printf('<option value="%d" %s>%s</option>', (int) $captain->ID, selected($captain_filter, (int) $captain->ID, false), esc_html($captain->post_title));
    }
    echo '</select></label></p>';
    echo '<p style="margin:0;"><label>Boat<br><select name="chbk_boat_id"><option value="">All Boats</option>';
    foreach ($boats as $boat) {
        printf('<option value="%d" %s>%s</option>', (int) $boat->ID, selected($boat_filter, (int) $boat->ID, false), esc_html($boat->post_title));
    }
    echo '</select></label></p>';
    echo '<p style="margin:0;"><label>Booking Status<br><select name="chbk_booking_status"><option value="">All Booking Statuses</option>';
    foreach ($booking_status_options as $status_value => $status_label) {
        printf('<option value="%s" %s>%s</option>', esc_attr((string) $status_value), selected($booking_status_filter, (string) $status_value, false), esc_html((string) $status_label));
    }
    echo '</select></label></p>';
    echo '<p style="margin:0;"><label>Payment Status<br><select name="chbk_payment_status"><option value="">All Payment Statuses</option>';
    foreach ($payment_status_options as $status_value => $status_label) {
        printf('<option value="%s" %s>%s</option>', esc_attr((string) $status_value), selected($payment_status_filter, (string) $status_value, false), esc_html((string) $status_label));
    }
    echo '</select></label></p>';
    echo '<p style="margin:0;"><label>Booking Slot Start<br><input type="text" class="chbk-datepicker" name="chbk_slot_start" value="' . esc_attr($slot_start) . '" placeholder="YYYY-MM-DD"></label></p>';
    echo '<p style="margin:0;"><label>Booking Slot End<br><input type="text" class="chbk-datepicker" name="chbk_slot_end" value="' . esc_attr($slot_end) . '" placeholder="YYYY-MM-DD"></label></p>';
    echo '<p style="margin:0;flex-basis:100%;"><button type="submit" class="button button-primary">Filter</button> <a class="button" href="' . esc_url($reset_url) . '">Reset</a></p>';
    echo '</form>';
    echo '<table class="widefat"><thead><tr><th>Trips</th><th>Captain</th><th>Boat</th><th>Booking Slot</th><th>Customer</th><th>Paid</th><th>Total</th><th>Booking Status</th><th>Payment Status</th><th>Actions</th></tr></thead><tbody>';
    foreach ($bookings as $booking) {
        $trip_id = (int) get_post_meta($booking->ID, 'chbk_trip_id', true);
        $captain_id = (int) get_post_meta($booking->ID, 'chbk_captain_id', true);
        $boat_id = (int) get_post_meta($booking->ID, 'chbk_boat_id', true);
        $trip_datetime_utc = (string) get_post_meta($booking->ID, 'chbk_trip_datetime_utc', true);
        $customer_name = (string) get_post_meta($booking->ID, 'chbk_customer_name', true);
        $customer_email = (string) get_post_meta($booking->ID, 'chbk_customer_email', true);
        $paid_total = function_exists('chbk_get_booking_paid_amount') ? chbk_get_booking_paid_amount($booking->ID) : 0.0;
        $total = get_post_meta($booking->ID, 'chbk_total_price', true);
        $booking_status = (string) get_post_meta($booking->ID, 'chbk_booking_status', true);
        $payment_status = (string) get_post_meta($booking->ID, 'chbk_payment_status', true);
        $payment_status_label = function_exists('chbk_humanize_slug') ? chbk_humanize_slug($payment_status) : ucwords(str_replace('_', ' ', $payment_status));
        $booking_status_options = function_exists('chbk_booking_status_options')
            ? chbk_booking_status_options($booking_status, 'booking')
            : [
                'pending' => 'Pending',
                'confirmed' => 'Confirmed',
                'cancelled' => 'Cancelled',
                'completed' => 'Completed',
                'no_show' => 'No Show',
            ];
        $booking_status_select = '<select class="chbk-booking-status-select" data-booking-id="' . (int) $booking->ID . '" data-nonce="' . esc_attr(wp_create_nonce('chbk_update_booking_status')) . '">';
        foreach ($booking_status_options as $value => $label) {
            $booking_status_select .= '<option value="' . esc_attr((string) $value) . '" ' . selected((string) $booking_status, (string) $value, false) . '>' . esc_html((string) $label) . '</option>';
        }
        $booking_status_select .= '</select>';
        $booking_status_select .= '<span class="chbk-booking-status-feedback description" style="margin-left:6px;display:none;"></span>';
        $booking_status_select_allowed = [
            'select' => [
                'class' => true,
                'data-booking-id' => true,
                'data-nonce' => true,
            ],
            'option' => [
                'value' => true,
                'selected' => true,
            ],
            'span' => [
                'class' => true,
                'style' => true,
            ],
        ];
        $actions = '';
        $actions .= sprintf(
            '<button type="button" class="button chbk-record-payment-btn" data-booking-id="%d" data-booking-title="%s" style="margin-right:4px">Record payment</button>',
            (int) $booking->ID,
            esc_attr($booking->post_title)
        );
        $resend_url = wp_nonce_url(
            admin_url('admin-post.php?action=chbk_resend_booking_email&booking_id=' . (int) $booking->ID),
            'chbk_resend_booking_email_' . (int) $booking->ID
        );
        $actions .= '<a class="button" style="margin-right:4px" href="' . esc_url($resend_url) . '">Resend email</a>';

        $captain_label = 'None';
        if ($captain_id > 0) {
            $captain_post = get_post($captain_id);
            if ($captain_post && $captain_post->post_type === 'chbk_captain') {
                $captain_label = $captain_post->post_title;
            }
        }

        $boat_label = 'None';
        if ($boat_id > 0) {
            $boat_post = get_post($boat_id);
            if ($boat_post && $boat_post->post_type === 'chbk_boat') {
                $boat_label = $boat_post->post_title;
            }
        }

        $slot_label = 'Not set';
        if ($trip_datetime_utc !== '') {
            $tz = $trip_id > 0 && function_exists('chbk_get_trip_timezone') ? chbk_get_trip_timezone($trip_id) : chbk_get_default_site_timezone();
            $local = chbk_from_utc_datetime($trip_datetime_utc, $tz);
            if ($local !== '') {
                try {
                    $dt = new DateTime($local, new DateTimeZone($tz));
                    $slot_label = $dt->format('M j, Y g:i a');
                } catch (Exception $e) {
                    $slot_label = $local;
                }
            }
        }

        $customer_label = trim($customer_name);
        if ($customer_email !== '') {
            $customer_label = trim($customer_label) !== '' ? ($customer_label . ' (' . $customer_email . ')') : $customer_email;
        }
        if ($customer_label === '') {
            $customer_label = 'N/A';
        }

        $paid_amount = (float) $paid_total;
        $total_amount = (float) $total;
        $remaining_amount = max(0, round($total_amount - $paid_amount, 2));
        $paid_cell_html = '<a href="' . esc_url(add_query_arg(['page' => 'chbk_payments', 'booking_id' => (int) $booking->ID], admin_url('admin.php'))) . '">' . esc_html(number_format($paid_amount, 2)) . '</a>';
        if ($remaining_amount > 0) {
            $paid_cell_html .= '<br><span class="description">' . esc_html(number_format($remaining_amount, 2)) . ' remaining</span>';
        }

        $trip_title_label = $trip_id > 0 ? get_the_title($trip_id) : '';
        if (!is_string($trip_title_label) || trim($trip_title_label) === '') {
            $trip_title_label = $booking->post_title;
        }
        $trip_cell_html = '<span>' . esc_html($trip_title_label) . '</span>';
        $trip_cell_html .= '<br><span class="description">Booked ' . esc_html(get_the_date('F j, Y', $booking->ID)) . '</span>';
        $trip_cell_html .= '<br><span class="description"><a href="' . esc_url(get_edit_post_link($booking->ID)) . '">View Booking</a></span>';

        printf(
            '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>',
            wp_kses_post($trip_cell_html),
            esc_html($captain_label),
            esc_html($boat_label),
            esc_html($slot_label),
            esc_html($customer_label),
            wp_kses_post($paid_cell_html),
            esc_html(number_format($total_amount, 2)),
            wp_kses($booking_status_select, $booking_status_select_allowed),
            esc_html($payment_status_label),
            wp_kses_post($actions)
        );
    }
    echo '</tbody></table>';
    echo '<div class="chbk-modal-backdrop" id="chbk-record-payment-modal">';
    echo '<div class="chbk-modal-panel">';
    echo '<button type="button" class="chbk-modal-close" aria-label="Close">&times;</button>';
    echo '<h2>Record Payment</h2>';
    echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="chbk-record-payment-form">';
    wp_nonce_field('chbk_record_booking_payment', 'chbk_record_payment_nonce');
    echo '<input type="hidden" name="action" value="chbk_record_booking_payment">';
    echo '<input type="hidden" name="booking_id" value="">';
    echo '<p><strong class="chbk-record-payment-booking"></strong></p>';
    echo '<p><label>Amount<br><input type="number" step="0.01" min="0.01" name="chbk_payment_amount" required></label></p>';
    echo '<p><label>Date Paid<br><input type="text" class="chbk-datepicker" name="chbk_payment_date" placeholder="YYYY-MM-DD" required></label></p>';
    echo '<p><label>Time Paid<br><input type="time" name="chbk_payment_time" value="' . esc_attr(current_time('H:i')) . '" required></label></p>';
    echo '<p><label>Payment Method<br><select name="chbk_payment_method">';
    echo '<option value="cash">Cash</option>';
    echo '<option value="check">Check</option>';
    echo '<option value="mobile_payment">Mobile Payment</option>';
    echo '<option value="stripe">Stripe</option>';
    echo '<option value="paypal">PayPal</option>';
    echo '</select></label></p>';
    echo '<p><button type="submit" class="button button-primary">Save Payment</button> <button type="button" class="button chbk-modal-cancel">Cancel</button></p>';
    echo '</form></div></div>';
    echo '</div>';
}

function chbk_handle_ajax_update_booking_status()
{
    $request_method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper(sanitize_text_field(wp_unslash((string) $_SERVER['REQUEST_METHOD']))) : '';
    if ($request_method !== 'POST') {
        wp_send_json_error(['message' => 'Invalid request method.'], 405);
    }

    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => 'Permission denied.'], 403);
    }

    check_ajax_referer('chbk_update_booking_status', 'nonce');

    $booking_id = isset($_POST['booking_id']) ? absint($_POST['booking_id']) : 0;
    $new_status = isset($_POST['booking_status']) ? sanitize_key(wp_unslash($_POST['booking_status'])) : '';
    if ($booking_id < 1 || $new_status === '') {
        wp_send_json_error(['message' => 'Missing booking status data.'], 400);
    }

    $booking = get_post($booking_id);
    if (!$booking || $booking->post_type !== 'chbk_booking') {
        wp_send_json_error(['message' => 'Booking not found.'], 404);
    }

    $current_status = (string) get_post_meta($booking_id, 'chbk_booking_status', true);
    $allowed = function_exists('chbk_booking_status_options')
        ? array_keys(chbk_booking_status_options($current_status, 'booking'))
        : ['pending', 'confirmed', 'cancelled', 'completed', 'no_show'];
    if (!in_array($new_status, $allowed, true)) {
        wp_send_json_error(['message' => 'Invalid booking status.'], 400);
    }

    if (function_exists('chbk_update_booking_status')) {
        chbk_update_booking_status($booking_id, $new_status);
    } else {
        update_post_meta($booking_id, 'chbk_booking_status', $new_status);
    }

    wp_send_json_success([
        'status' => $new_status,
        'label' => function_exists('chbk_humanize_slug') ? chbk_humanize_slug($new_status) : ucwords(str_replace('_', ' ', $new_status)),
    ]);
}

function chbk_render_payments_page()
{
    $filter_booking_id = isset($_GET['booking_id']) ? absint($_GET['booking_id']) : 0;
    $search = isset($_GET['chbk_payment_search']) ? sanitize_text_field(wp_unslash($_GET['chbk_payment_search'])) : '';
    $method_filter = isset($_GET['chbk_payment_method']) ? sanitize_key(wp_unslash($_GET['chbk_payment_method'])) : '';
    $date_start = isset($_GET['chbk_payment_date_start']) ? sanitize_text_field(wp_unslash($_GET['chbk_payment_date_start'])) : '';
    $date_end = isset($_GET['chbk_payment_date_end']) ? sanitize_text_field(wp_unslash($_GET['chbk_payment_date_end'])) : '';
    $amount_min_raw = isset($_GET['chbk_payment_amount_min']) ? sanitize_text_field(wp_unslash($_GET['chbk_payment_amount_min'])) : '';
    $amount_max_raw = isset($_GET['chbk_payment_amount_max']) ? sanitize_text_field(wp_unslash($_GET['chbk_payment_amount_max'])) : '';
    $amount_min = is_numeric($amount_min_raw) ? (float) $amount_min_raw : null;
    $amount_max = is_numeric($amount_max_raw) ? (float) $amount_max_raw : null;
    $method_options = [
        'cash' => 'Cash',
        'check' => 'Check',
        'mobile_payment' => 'Mobile Payment',
        'stripe' => 'Stripe',
        'paypal' => 'PayPal',
    ];
    if ($method_filter !== '' && !isset($method_options[$method_filter])) {
        $method_filter = '';
    }
    $has_start = preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_start) === 1;
    $has_end = preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_end) === 1;
    $start_ts = $has_start ? strtotime($date_start . ' 00:00:00') : false;
    $end_ts = $has_end ? strtotime($date_end . ' 23:59:59') : false;
    $bookings = get_posts([
        'post_type' => 'chbk_booking',
        'numberposts' => 200,
        'post_status' => 'publish',
    ]);

    echo '<div class="wrap"><h1>Payments</h1>';
    if (isset($_GET['chbk_payment_notice'])) {
        $payment_notice = sanitize_key(wp_unslash($_GET['chbk_payment_notice']));
        if ($payment_notice === 'updated') {
            echo '<div class="notice notice-success is-dismissible"><p>Payment updated.</p></div>';
        } elseif ($payment_notice === 'deleted') {
            echo '<div class="notice notice-success is-dismissible"><p>Payment deleted.</p></div>';
        } elseif ($payment_notice === 'failed') {
            echo '<div class="notice notice-error is-dismissible"><p>Unable to update payment.</p></div>';
        } elseif ($payment_notice === 'delete_failed') {
            echo '<div class="notice notice-error is-dismissible"><p>Unable to delete payment.</p></div>';
        }
    }
    if ($filter_booking_id > 0) {
        $clear_url = add_query_arg(['page' => 'chbk_payments'], admin_url('admin.php'));
        echo '<p><strong>Showing payments for booking #' . (int) $filter_booking_id . '.</strong> <a href="' . esc_url($clear_url) . '">Clear filter</a></p>';
    }
    $reset_url = add_query_arg(['page' => 'chbk_payments'], admin_url('admin.php'));
    echo '<form method="get" style="margin:1rem 0;display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;">';
    echo '<input type="hidden" name="page" value="chbk_payments">';
    if ($filter_booking_id > 0) {
        echo '<input type="hidden" name="booking_id" value="' . (int) $filter_booking_id . '">';
    }
    echo '<p style="margin:0;"><label>Search<br><input type="text" name="chbk_payment_search" value="' . esc_attr($search) . '" placeholder="Customer name or email"></label></p>';
    echo '<p style="margin:0;"><label>Method<br><select name="chbk_payment_method"><option value="">All Methods</option>';
    foreach ($method_options as $method_value => $method_label) {
        printf('<option value="%s" %s>%s</option>', esc_attr((string) $method_value), selected($method_filter, (string) $method_value, false), esc_html((string) $method_label));
    }
    echo '</select></label></p>';
    echo '<p style="margin:0;"><label>Date Start<br><input type="text" class="chbk-datepicker" name="chbk_payment_date_start" value="' . esc_attr($date_start) . '" placeholder="YYYY-MM-DD"></label></p>';
    echo '<p style="margin:0;"><label>Date End<br><input type="text" class="chbk-datepicker" name="chbk_payment_date_end" value="' . esc_attr($date_end) . '" placeholder="YYYY-MM-DD"></label></p>';
    echo '<p style="margin:0;"><label>Amount Min<br><input type="number" step="0.01" min="0" name="chbk_payment_amount_min" value="' . esc_attr($amount_min_raw) . '" placeholder="0.00"></label></p>';
    echo '<p style="margin:0;"><label>Amount Max<br><input type="number" step="0.01" min="0" name="chbk_payment_amount_max" value="' . esc_attr($amount_max_raw) . '" placeholder="0.00"></label></p>';
    echo '<p style="margin:0;flex-basis:100%;"><button type="submit" class="button button-primary">Filter</button> <a class="button" href="' . esc_url($reset_url) . '">Reset</a></p>';
    echo '</form>';

    $rows = [];
    foreach ($bookings as $booking) {
        if ($filter_booking_id > 0 && (int) $booking->ID !== $filter_booking_id) {
            continue;
        }
        $ledger = function_exists('chbk_get_payment_ledger') ? chbk_get_payment_ledger($booking->ID) : [];
        if (empty($ledger)) {
            continue;
        }

        $customer_name = (string) get_post_meta($booking->ID, 'chbk_customer_name', true);
        $customer_email = (string) get_post_meta($booking->ID, 'chbk_customer_email', true);
        $customer_label = trim($customer_name);
        if ($customer_email !== '') {
            $customer_label = trim($customer_label) !== '' ? ($customer_label . ' (' . $customer_email . ')') : $customer_email;
        }
        if ($customer_label === '') {
            $customer_label = 'N/A';
        }

        foreach ($ledger as $entry_index => $entry) {
            if (!is_array($entry)) {
                continue;
            }
            $date = isset($entry['date']) ? (string) $entry['date'] : '';
            $amount = isset($entry['amount']) ? (float) $entry['amount'] : 0;
            $method = isset($entry['method']) ? (string) $entry['method'] : 'manual';
            $source = isset($entry['source']) ? (string) $entry['source'] : 'admin';
            $reference = isset($entry['reference']) ? (string) $entry['reference'] : '';
            $note = isset($entry['note']) ? (string) $entry['note'] : '';

            if ($search !== '' && stripos($customer_name, $search) === false && stripos($customer_email, $search) === false) {
                continue;
            }
            if ($method_filter !== '' && $method !== $method_filter) {
                continue;
            }
            if ($amount_min !== null && $amount < $amount_min) {
                continue;
            }
            if ($amount_max !== null && $amount > $amount_max) {
                continue;
            }
            if ($start_ts !== false || $end_ts !== false) {
                $entry_ts = strtotime($date);
                if (!$entry_ts) {
                    continue;
                }
                if ($start_ts !== false && $entry_ts < $start_ts) {
                    continue;
                }
                if ($end_ts !== false && $entry_ts > $end_ts) {
                    continue;
                }
            }

            $rows[] = [
                'booking_id' => (int) $booking->ID,
                'booking_title' => (string) $booking->post_title,
                'customer' => $customer_label,
                'date' => $date,
                'amount' => $amount,
                'method' => $method,
                'source' => $source,
                'reference' => $reference,
                'note' => $note,
                'entry_index' => (int) $entry_index,
            ];
        }
    }

    usort($rows, function ($a, $b) {
        $a_date = isset($a['date']) ? strtotime((string) $a['date']) : 0;
        $b_date = isset($b['date']) ? strtotime((string) $b['date']) : 0;
        return $b_date <=> $a_date;
    });

    echo '<table class="widefat"><thead><tr><th>Date</th><th>Booking</th><th>Customer</th><th>Amount</th><th>Method</th><th>Source</th><th>Reference</th><th>Note</th><th>Actions</th></tr></thead><tbody>';
    if (empty($rows)) {
        echo '<tr><td colspan="9">No payment entries yet.</td></tr>';
    } else {
        foreach ($rows as $row) {
            $date_label = (string) $row['date'];
            if ($date_label !== '') {
                $ts = strtotime($date_label);
                if ($ts) {
                    $date_label = wp_date('M j, Y g:i a', $ts);
                }
            }
            $delete_url = wp_nonce_url(
                add_query_arg(
                    [
                        'action' => 'chbk_delete_booking_payment',
                        'booking_id' => (int) $row['booking_id'],
                        'entry_index' => (int) $row['entry_index'],
                        'redirect_page' => 'chbk_payments',
                    ],
                    admin_url('admin-post.php')
                ),
                'chbk_delete_booking_payment_' . (int) $row['booking_id'] . '_' . (int) $row['entry_index']
            );
            $action_buttons = sprintf(
                '<button type="button" class="button chbk-edit-payment-btn" data-booking-id="%d" data-booking-title="%s" data-entry-index="%d" data-payment-amount="%s" data-payment-date="%s" data-payment-time="%s" data-payment-method="%s" data-payment-reference="%s" data-payment-note="%s" style="margin-right:4px">Edit</button><a class="button button-link-delete" href="%s" onclick="return confirm(\'Delete this payment entry?\');">Delete</a>',
                (int) $row['booking_id'],
                esc_attr((string) $row['booking_title']),
                (int) $row['entry_index'],
                esc_attr((string) number_format((float) $row['amount'], 2, '.', '')),
                esc_attr(substr((string) $row['date'], 0, 10)),
                esc_attr(substr((string) $row['date'], 11, 5) !== '' ? substr((string) $row['date'], 11, 5) : '00:00'),
                esc_attr((string) $row['method']),
                esc_attr((string) $row['reference']),
                esc_attr((string) $row['note']),
                esc_url($delete_url)
            );
            printf(
                '<tr><td>%s</td><td><a href="%s">%s</a></td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>',
                esc_html($date_label !== '' ? $date_label : 'N/A'),
                esc_url(get_edit_post_link((int) $row['booking_id'])),
                esc_html((string) $row['booking_title']),
                esc_html((string) $row['customer']),
                esc_html(number_format((float) $row['amount'], 2)),
                esc_html(function_exists('chbk_humanize_slug') ? chbk_humanize_slug((string) $row['method']) : (string) $row['method']),
                esc_html(function_exists('chbk_humanize_slug') ? chbk_humanize_slug((string) $row['source']) : (string) $row['source']),
                esc_html((string) $row['reference']),
                esc_html((string) $row['note']),
                wp_kses_post($action_buttons)
            );
        }
    }
    echo '</tbody></table>';

    echo '<div class="chbk-modal-backdrop" id="chbk-edit-payment-modal">';
    echo '<div class="chbk-modal-panel">';
    echo '<button type="button" class="chbk-modal-close" aria-label="Close">&times;</button>';
    echo '<h2>Edit Payment</h2>';
    echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="chbk-edit-payment-form">';
    wp_nonce_field('chbk_update_booking_payment', 'chbk_update_payment_nonce');
    echo '<input type="hidden" name="action" value="chbk_update_booking_payment">';
    echo '<input type="hidden" name="booking_id" value="">';
    echo '<input type="hidden" name="entry_index" value="">';
    echo '<input type="hidden" name="redirect_page" value="chbk_payments">';
    echo '<p><strong class="chbk-edit-payment-booking"></strong></p>';
    echo '<p><label>Amount<br><input type="number" step="0.01" min="0.01" name="chbk_payment_amount" required></label></p>';
    echo '<p><label>Date Paid<br><input type="text" class="chbk-datepicker" name="chbk_payment_date" placeholder="YYYY-MM-DD" required></label></p>';
    echo '<p><label>Time Paid<br><input type="time" name="chbk_payment_time" value="' . esc_attr(current_time('H:i')) . '" required></label></p>';
    echo '<p><label>Payment Method<br><select name="chbk_payment_method">';
    echo '<option value="cash">Cash</option>';
    echo '<option value="check">Check</option>';
    echo '<option value="mobile_payment">Mobile Payment</option>';
    echo '<option value="stripe">Stripe</option>';
    echo '<option value="paypal">PayPal</option>';
    echo '</select></label></p>';
    echo '<p><label>Reference (optional)<br><input type="text" name="chbk_payment_reference"></label></p>';
    echo '<p><label>Note (optional)<br><input type="text" name="chbk_payment_note"></label></p>';
    echo '<p><button type="submit" class="button button-primary">Update Payment</button> <button type="button" class="button chbk-modal-cancel">Cancel</button></p>';
    echo '</form></div></div>';
    echo '</div>';
}

function chbk_handle_resend_booking_email()
{
    if (!current_user_can('manage_options')) {
        wp_die('Unauthorized');
    }

    $booking_id = isset($_GET['booking_id']) ? absint($_GET['booking_id']) : 0;
    if (!$booking_id || get_post_type($booking_id) !== 'chbk_booking') {
        wp_die('Invalid booking');
    }

    check_admin_referer('chbk_resend_booking_email_' . $booking_id);
    $sent = function_exists('chbk_send_booking_confirmation_email') ? chbk_send_booking_confirmation_email($booking_id) : false;

    $notice = $sent ? 'resent' : 'resent_failed';
    wp_safe_redirect(add_query_arg(['page' => 'chbk_bookings', 'chbk_booking_notice' => $notice], admin_url('admin.php')));
    exit;
}

function chbk_handle_record_booking_payment()
{
    $request_method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper(sanitize_text_field(wp_unslash((string) $_SERVER['REQUEST_METHOD']))) : '';
    if ($request_method !== 'POST') {
        wp_die('Invalid request method');
    }

    if (!current_user_can('manage_options')) {
        wp_die('Unauthorized');
    }

    check_admin_referer('chbk_record_booking_payment', 'chbk_record_payment_nonce');

    $booking_id = isset($_POST['booking_id']) ? absint($_POST['booking_id']) : 0;
    if (!$booking_id || get_post_type($booking_id) !== 'chbk_booking') {
        wp_safe_redirect(add_query_arg(['page' => 'chbk_bookings', 'chbk_booking_notice' => 'payment_failed'], admin_url('admin.php')));
        exit;
    }

    $amount_raw = isset($_POST['chbk_payment_amount']) ? sanitize_text_field(wp_unslash($_POST['chbk_payment_amount'])) : '';
    $amount = is_numeric($amount_raw) ? (float) $amount_raw : 0.0;
    $date = isset($_POST['chbk_payment_date']) ? sanitize_text_field(wp_unslash($_POST['chbk_payment_date'])) : '';
    $time = isset($_POST['chbk_payment_time']) ? sanitize_text_field(wp_unslash($_POST['chbk_payment_time'])) : '00:00';
    $method = isset($_POST['chbk_payment_method']) ? sanitize_key(wp_unslash($_POST['chbk_payment_method'])) : 'cash';
    $allowed_methods = ['cash', 'check', 'mobile_payment', 'stripe', 'paypal'];
    if (!in_array($method, $allowed_methods, true)) {
        $method = 'cash';
    }

    if ($amount <= 0 || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) || !preg_match('/^\d{2}:\d{2}$/', $time)) {
        wp_safe_redirect(add_query_arg(['page' => 'chbk_bookings', 'chbk_booking_notice' => 'payment_failed'], admin_url('admin.php')));
        exit;
    }

    $entry_date = $date . ' ' . $time . ':00';
    $ok = function_exists('chbk_append_payment_ledger_entry') ? chbk_append_payment_ledger_entry($booking_id, [
        'amount' => $amount,
        'date' => $entry_date,
        'method' => $method,
        'source' => 'admin',
        'note' => 'Recorded from Bookings page',
    ]) : false;

    wp_safe_redirect(add_query_arg(['page' => 'chbk_bookings', 'chbk_booking_notice' => $ok ? 'payment_recorded' : 'payment_failed'], admin_url('admin.php')));
    exit;
}

function chbk_handle_update_booking_payment()
{
    $request_method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper(sanitize_text_field(wp_unslash((string) $_SERVER['REQUEST_METHOD']))) : '';
    if ($request_method !== 'POST') {
        wp_die('Invalid request method');
    }

    if (!current_user_can('manage_options')) {
        wp_die('Unauthorized');
    }

    check_admin_referer('chbk_update_booking_payment', 'chbk_update_payment_nonce');

    $booking_id = isset($_POST['booking_id']) ? absint($_POST['booking_id']) : 0;
    $entry_index = isset($_POST['entry_index']) ? absint($_POST['entry_index']) : -1;
    $redirect_page = isset($_POST['redirect_page']) ? sanitize_key(wp_unslash($_POST['redirect_page'])) : 'chbk_payments';
    if (!$booking_id || get_post_type($booking_id) !== 'chbk_booking' || $entry_index < 0) {
        wp_safe_redirect(add_query_arg(['page' => $redirect_page, 'chbk_payment_notice' => 'failed'], admin_url('admin.php')));
        exit;
    }

    $amount_raw = isset($_POST['chbk_payment_amount']) ? sanitize_text_field(wp_unslash($_POST['chbk_payment_amount'])) : '';
    $amount = is_numeric($amount_raw) ? (float) $amount_raw : 0.0;
    $date = isset($_POST['chbk_payment_date']) ? sanitize_text_field(wp_unslash($_POST['chbk_payment_date'])) : '';
    $time = isset($_POST['chbk_payment_time']) ? sanitize_text_field(wp_unslash($_POST['chbk_payment_time'])) : '00:00';
    $method = isset($_POST['chbk_payment_method']) ? sanitize_key(wp_unslash($_POST['chbk_payment_method'])) : 'cash';
    $reference = isset($_POST['chbk_payment_reference']) ? sanitize_text_field(wp_unslash($_POST['chbk_payment_reference'])) : '';
    $note = isset($_POST['chbk_payment_note']) ? sanitize_text_field(wp_unslash($_POST['chbk_payment_note'])) : '';

    $allowed_methods = ['cash', 'check', 'mobile_payment', 'stripe', 'paypal'];
    if (!in_array($method, $allowed_methods, true)) {
        $method = 'cash';
    }
    if ($amount <= 0 || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) || !preg_match('/^\d{2}:\d{2}$/', $time)) {
        wp_safe_redirect(add_query_arg(['page' => $redirect_page, 'chbk_payment_notice' => 'failed'], admin_url('admin.php')));
        exit;
    }

    $ok = function_exists('chbk_update_payment_ledger_entry') ? chbk_update_payment_ledger_entry($booking_id, $entry_index, [
        'amount' => $amount,
        'date' => $date . ' ' . $time . ':00',
        'method' => $method,
        'source' => 'admin',
        'reference' => $reference,
        'note' => $note,
    ]) : false;

    $args = ['page' => $redirect_page, 'chbk_payment_notice' => $ok ? 'updated' : 'failed'];
    if ($booking_id > 0) {
        $args['booking_id'] = $booking_id;
    }
    wp_safe_redirect(add_query_arg($args, admin_url('admin.php')));
    exit;
}

function chbk_handle_delete_booking_payment()
{
    if (!current_user_can('manage_options')) {
        wp_die('Unauthorized');
    }

    $booking_id = isset($_GET['booking_id']) ? absint($_GET['booking_id']) : 0;
    $entry_index = isset($_GET['entry_index']) ? absint($_GET['entry_index']) : -1;
    $redirect_page = isset($_GET['redirect_page']) ? sanitize_key(wp_unslash($_GET['redirect_page'])) : 'chbk_payments';

    if (!$booking_id || get_post_type($booking_id) !== 'chbk_booking' || $entry_index < 0) {
        wp_safe_redirect(add_query_arg(['page' => $redirect_page, 'chbk_payment_notice' => 'delete_failed'], admin_url('admin.php')));
        exit;
    }

    check_admin_referer('chbk_delete_booking_payment_' . $booking_id . '_' . $entry_index);
    $ok = function_exists('chbk_delete_payment_ledger_entry') ? chbk_delete_payment_ledger_entry($booking_id, $entry_index) : false;

    $args = ['page' => $redirect_page, 'chbk_payment_notice' => $ok ? 'deleted' : 'delete_failed'];
    if ($booking_id > 0) {
        $args['booking_id'] = $booking_id;
    }
    wp_safe_redirect(add_query_arg($args, admin_url('admin.php')));
    exit;
}

function chbk_render_settings_page()
{
    if (!current_user_can('manage_options')) {
        return;
    }

    $tabs = chbk_get_settings_tabs();
    $active_tab = chbk_get_current_settings_tab();
    $base_url = admin_url('admin.php?page=chbk_settings');

    echo '<div class="wrap"><h1>Charter Booker Settings</h1>';
    echo '<h2 class="nav-tab-wrapper">';
    foreach ($tabs as $tab_key => $tab_label) {
        $tab_url = add_query_arg(['page' => 'chbk_settings', 'tab' => $tab_key], admin_url('admin.php'));
        $classes = 'nav-tab' . ($active_tab === $tab_key ? ' nav-tab-active' : '');
        printf('<a href="%s" class="%s">%s</a>', esc_url($tab_url), esc_attr($classes), esc_html($tab_label));
    }
    echo '</h2>';

    if ($active_tab === 'general') {
        chbk_render_settings_general_tab();
    } elseif ($active_tab === 'emails') {
        chbk_render_settings_emails_tab();
    } elseif ($active_tab === 'shortcodes') {
        chbk_render_settings_shortcodes_tab();
    } else {
        do_action('chbk_render_settings_tab_' . $active_tab, $base_url);
    }

    echo '</div>';
}

function chbk_get_settings_tabs()
{
    $tabs = [
        'general' => 'General',
        'emails' => 'Emails',
        'shortcodes' => 'Shortcodes',
    ];

    return (array) apply_filters('chbk_settings_tabs', $tabs);
}

function chbk_get_current_settings_tab()
{
    $tabs = chbk_get_settings_tabs();
    $requested = isset($_GET['tab']) ? sanitize_key(wp_unslash($_GET['tab'])) : 'general';
    if (!isset($tabs[$requested])) {
        return 'general';
    }
    return $requested;
}

function chbk_render_settings_general_tab()
{
    $settings = get_option('chbk_settings', chbk_get_default_settings());
    $defaults = chbk_get_default_settings();
    $currencies = chbk_get_supported_currencies();
    $selected_currency = isset($settings['currency']) ? strtoupper((string) $settings['currency']) : 'USD';
    if (!isset($currencies[$selected_currency])) {
        $selected_currency = 'USD';
    }
    $payment_message = isset($settings['payment_message']) ? (string) $settings['payment_message'] : '';
    if (trim($payment_message) === '') {
        $payment_message = (string) $defaults['payment_message'];
    }
    $book_now_label = isset($settings['trip_card_book_now_label']) ? trim((string) $settings['trip_card_book_now_label']) : '';
    if ($book_now_label === '') {
        $book_now_label = isset($defaults['trip_card_book_now_label']) ? (string) $defaults['trip_card_book_now_label'] : 'Book Now';
    }

    echo '<form method="post" action="options.php">';
    settings_fields('chbk_settings_group');

    echo '<table class="form-table">';
    echo '<tr><th>Currency</th><td><select name="chbk_settings[currency]">';
    foreach ($currencies as $code => $label) {
        printf('<option value="%s" %s>%s</option>', esc_attr((string) $code), selected($selected_currency, $code, false), esc_html((string) $label));
    }
    echo '</select></td></tr>';
    printf('<tr><th>Trips URL Path</th><td><input name="chbk_settings[trips_slug]" value="%s" class="regular-text" placeholder="charters"><p class="description">Example: <code>charters</code> makes trip URLs like /charters/trip-name.</p></td></tr>', esc_attr(isset($settings['trips_slug']) ? $settings['trips_slug'] : 'charters'));
    printf('<tr><th>Trip Listing Book Button Label</th><td><input name="chbk_settings[trip_card_book_now_label]" value="%s" class="regular-text" placeholder="Book Now"><p class="description">Used for the call-to-action text on trip listing cards.</p></td></tr>', esc_attr($book_now_label));
    printf('<tr><th>Payment Phone Number</th><td><input name="chbk_settings[payment_phone_number]" value="%s" class="regular-text" placeholder="(555) 123-4567"><p class="description">Used for the <code>{{phone_number}}</code> variable in payment instructions.</p></td></tr>', esc_attr(isset($settings['payment_phone_number']) ? $settings['payment_phone_number'] : ''));
    printf('<tr><th>Global Payment Message</th><td><textarea name="chbk_settings[payment_message]" class="large-text" rows="3">%s</textarea><p class="description">Shown after booking in free mode. Available variable: <code>{{phone_number}}</code>.</p></td></tr>', esc_textarea($payment_message));
    echo '</table>';

    submit_button('Save changes');
    echo '</form>';
}

function chbk_render_settings_emails_tab()
{
    $settings = get_option('chbk_settings', chbk_get_default_settings());

    echo '<form method="post" action="options.php">';
    settings_fields('chbk_settings_group');

    $fields = [
        'email_booking_confirmation_enabled' => 'Enable Booking Confirmation',
        'email_admin_notification_enabled' => 'Enable Admin Notification',
    ];

    echo '<table class="form-table">';
    foreach ($fields as $field => $label) {
        printf('<tr><th>%s</th><td><input type="checkbox" name="chbk_settings[%s]" value="1" %s></td></tr>', esc_html($label), esc_attr($field), checked((int) $settings[$field], 1, false));
    }

    printf('<tr><th>Booking Confirmation Subject</th><td><input class="large-text" name="chbk_settings[email_booking_confirmation_subject]" value="%s"></td></tr>', esc_attr($settings['email_booking_confirmation_subject']));
    printf('<tr><th>Booking Confirmation HTML</th><td><textarea class="large-text" rows="5" name="chbk_settings[email_booking_confirmation_html]">%s</textarea></td></tr>', esc_textarea($settings['email_booking_confirmation_html']));
    printf('<tr><th>Booking Confirmation Text</th><td><textarea class="large-text" rows="4" name="chbk_settings[email_booking_confirmation_text]">%s</textarea></td></tr>', esc_textarea($settings['email_booking_confirmation_text']));
    printf('<tr><th>Admin Notification Subject</th><td><input class="large-text" name="chbk_settings[email_admin_notification_subject]" value="%s"></td></tr>', esc_attr($settings['email_admin_notification_subject']));
    printf('<tr><th>Admin Notification HTML</th><td><textarea class="large-text" rows="5" name="chbk_settings[email_admin_notification_html]">%s</textarea></td></tr>', esc_textarea($settings['email_admin_notification_html']));
    printf('<tr><th>Admin Notification Text</th><td><textarea class="large-text" rows="4" name="chbk_settings[email_admin_notification_text]">%s</textarea></td></tr>', esc_textarea($settings['email_admin_notification_text']));
    echo '</table>';

    echo '<p>Available variables: {{booking_id}}, {{trip_title}}, {{customer_name}}, {{customer_email}}, {{total_price}}, {{deposit_due}}, {{phone_number}}, {{payment_message}}</p>';
    submit_button('Save changes');
    echo '</form>';
}

function chbk_render_settings_shortcodes_tab()
{
    echo '<div class="chbk-shortcodes-tab">';
    echo '<h2>Available Shortcodes</h2>';
    echo '<table class="form-table">';
    echo '<tr><th><code>[chbk_trip_search]</code></th><td><p>Displays searchable/public trip listing cards.</p><p class="description">Example: <code>[chbk_trip_search]</code></p></td></tr>';
    echo '<tr><th><code>[chbk_booking_form]</code></th><td><p>Displays booking form for the current trip when used on a single trip template.</p><p class="description">Use this in your single trip template content/layout.</p></td></tr>';
    echo '<tr><th><code>[chbk_booking_form trip_id="123"]</code></th><td><p>Displays booking form for a specific trip.</p><p class="description">Replace <code>123</code> with the trip post ID.</p></td></tr>';
    echo '</table>';
    echo '</div>';
}

function chbk_render_emails_page()
{
    if (!current_user_can('manage_options')) {
        return;
    }

    wp_safe_redirect(add_query_arg(['page' => 'chbk_settings', 'tab' => 'emails'], admin_url('admin.php')));
    exit;
}

function chbk_validate_trip_overlaps_on_save($post_id, $post, $update)
{
    if (wp_is_post_revision($post_id)) {
        return;
    }

    if (!current_user_can('edit_post', $post_id)) {
        return;
    }

    if (!function_exists('chbk_find_overlapping_trips')) {
        return;
    }

    $overlaps = chbk_find_overlapping_trips($post_id);
    if (empty($overlaps)) {
        delete_transient('chbk_trip_overlap_notice_' . get_current_user_id());
        return;
    }

    $titles = array_map(function ($post_obj) {
        return $post_obj->post_title;
    }, $overlaps);

    $message = 'Warning: This trip overlaps with other trips for the same captain/boat: ' . implode(', ', $titles) . '.';
    set_transient('chbk_trip_overlap_notice_' . get_current_user_id(), $message, 120);
}

function chbk_render_trip_overlap_notice()
{
    if (!is_admin()) {
        return;
    }

    $notice = get_transient('chbk_trip_overlap_notice_' . get_current_user_id());
    if (!$notice) {
        return;
    }

    $screen = function_exists('get_current_screen') ? get_current_screen() : null;
    if (!$screen || $screen->post_type !== 'chbk_trip') {
        return;
    }

    delete_transient('chbk_trip_overlap_notice_' . get_current_user_id());
    echo '<div class="notice notice-warning"><p>' . esc_html($notice) . '</p></div>';
}

function chbk_add_trip_clone_row_action($actions, $post)
{
    if (!$post || $post->post_type !== 'chbk_trip') {
        return $actions;
    }

    if (!current_user_can('edit_post', $post->ID)) {
        return $actions;
    }

    $url = wp_nonce_url(
        admin_url('admin.php?action=chbk_clone_trip&post=' . (int) $post->ID),
        'chbk_clone_trip_' . (int) $post->ID
    );

    $actions['chbk_clone_trip'] = '<a href="' . esc_url($url) . '">Clone</a>';
    return $actions;
}

function chbk_handle_trip_clone_action()
{
    if (!current_user_can('edit_posts')) {
        wp_die('Unauthorized');
    }

    $source_id = isset($_GET['post']) ? absint($_GET['post']) : 0;
    if (!$source_id) {
        wp_die('Invalid trip');
    }

    $source = get_post($source_id);
    if (!$source || $source->post_type !== 'chbk_trip') {
        wp_die('Trip not found');
    }

    check_admin_referer('chbk_clone_trip_' . $source_id);

    if (!current_user_can('edit_post', $source_id)) {
        wp_die('Unauthorized');
    }

    $new_id = wp_insert_post([
        'post_type' => 'chbk_trip',
        'post_status' => 'draft',
        'post_title' => $source->post_title . ' (Copy)',
        'post_content' => $source->post_content,
        'post_excerpt' => $source->post_excerpt,
        'post_author' => get_current_user_id(),
        'menu_order' => $source->menu_order,
    ]);

    if (is_wp_error($new_id) || !$new_id) {
        wp_die('Unable to clone trip');
    }

    $meta_map = get_post_meta($source_id);
    foreach ($meta_map as $meta_key => $meta_values) {
        if (in_array($meta_key, ['_edit_lock', '_edit_last'], true)) {
            continue;
        }
        foreach ((array) $meta_values as $meta_value) {
            add_post_meta($new_id, $meta_key, maybe_unserialize($meta_value));
        }
    }

    $taxonomies = get_object_taxonomies('chbk_trip');
    foreach ($taxonomies as $taxonomy) {
        $terms = wp_get_object_terms($source_id, $taxonomy, ['fields' => 'ids']);
        if (!is_wp_error($terms)) {
            wp_set_object_terms($new_id, $terms, $taxonomy, false);
        }
    }

    wp_safe_redirect(
        add_query_arg(
            [
                'page' => 'chbk_trips',
                'chbk_cloned' => 1,
                'chbk_cloned_id' => (int) $new_id,
            ],
            admin_url('admin.php')
        )
    );
    exit;
}

function chbk_render_trip_clone_notice()
{
    if (!is_admin()) {
        return;
    }

    if (!isset($_GET['chbk_cloned']) || (int) $_GET['chbk_cloned'] !== 1) {
        return;
    }

    $id = isset($_GET['chbk_cloned_id']) ? absint($_GET['chbk_cloned_id']) : 0;
    if (!$id) {
        return;
    }

    $edit_url = get_edit_post_link($id);
    if (!$edit_url) {
        return;
    }

    echo '<div class="notice notice-success is-dismissible"><p>Trip cloned. <a href="' . esc_url($edit_url) . '">Edit clone</a>.</p></div>';
}
