<?php

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

function chbk_register_meta()
{
    $meta = [
        'chbk_captain' => [
            'user_id' => 'integer',
            'timezone' => 'string',
            'status' => 'string',
            'blackout_rules' => 'string',
        ],
        'chbk_boat' => [
            'captain_id' => 'integer',
            'status' => 'string',
            'max_capacity' => 'integer',
            'min_capacity' => 'integer',
            'allows_overbooking' => 'boolean',
            'boat_features' => 'string',
            'blackout_rules' => 'string',
        ],
        'chbk_trip' => [
            'captain_id' => 'integer',
            'boat_id' => 'integer',
            'trip_timezone_override' => 'string',
            'trip_type' => 'string',
            'duration' => 'string',
            'base_price' => 'number',
            'seat_price' => 'number',
            'included_seats_enabled' => 'boolean',
            'included_seats_count' => 'integer',
            'online_payment_due_at_booking' => 'boolean',
            'online_payment_deposit_amount' => 'number',
            'online_payment_deposit_type' => 'string',
            'online_payment_must_pay_full' => 'boolean',
            'pricing_rules' => 'string',
            'deposit_rules' => 'string',
            'seasonality_rules' => 'string',
            'active_status' => 'string',
            'schedule_weekdays' => 'string',
            'schedule_start_time' => 'string',
            'schedule_duration_minutes' => 'integer',
            'single_booking_per_slot' => 'boolean',
            'children_not_allowed' => 'boolean',
            'season_start_date' => 'string',
            'season_end_date' => 'string',
            'blackout_rules' => 'string',
            'booking_window_days' => 'integer',
            'payment_message_override' => 'string',
        ],
        'chbk_booking' => [
            'trip_id' => 'integer',
            'boat_id' => 'integer',
            'captain_id' => 'integer',
            'user_id' => 'integer',
            'party_size' => 'integer',
            'booking_status' => 'string',
            'payment_status' => 'string',
            'order_id' => 'integer',
            'checkin_status' => 'string',
            'trip_datetime_utc' => 'string',
            'total_price' => 'number',
            'deposit_due' => 'number',
            'customer_name' => 'string',
            'customer_email' => 'string',
        ],
    ];

    foreach ($meta as $post_type => $fields) {
        foreach ($fields as $key => $type) {
            register_post_meta($post_type, 'chbk_' . $key, [
                'type' => $type,
                'single' => true,
                'show_in_rest' => true,
                'sanitize_callback' => 'chbk_sanitize_meta_value',
                'auth_callback' => function () {
                    return current_user_can('edit_posts');
                },
            ]);
        }
    }
}

function chbk_sanitize_meta_value($value)
{
    if (is_array($value)) {
        return wp_json_encode($value);
    }

    if (is_numeric($value)) {
        return $value + 0;
    }

    return sanitize_text_field((string) $value);
}

add_action('add_meta_boxes', 'chbk_add_meta_boxes');

function chbk_add_meta_boxes()
{
    add_meta_box('chbk_captain_meta', 'Captain Details', 'chbk_render_captain_meta_box', 'chbk_captain', 'normal');
    add_meta_box('chbk_boat_meta', 'Boat Details', 'chbk_render_boat_meta_box', 'chbk_boat', 'normal');
    add_meta_box('chbk_trip_meta', 'Trip Configuration', 'chbk_render_trip_meta_box', 'chbk_trip', 'normal');
    add_meta_box('chbk_booking_meta', 'Booking Details', 'chbk_render_booking_meta_box', 'chbk_booking', 'normal');
}

function chbk_get_blackout_rules_for_post($post_id, $has_time)
{
    $rules = chbk_parse_json_meta(get_post_meta($post_id, 'chbk_blackout_rules', true));
    $normalized = [];

    foreach ($rules as $rule) {
        if (!is_array($rule)) {
            continue;
        }
        $start_date = isset($rule['start_date']) ? sanitize_text_field((string) $rule['start_date']) : '';
        $end_date = isset($rule['end_date']) ? sanitize_text_field((string) $rule['end_date']) : '';
        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $start_date) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $end_date)) {
            continue;
        }
        $row = [
            'start_date' => $start_date,
            'end_date' => $end_date,
        ];
        if ($has_time) {
            $start_time = isset($rule['start_time']) ? sanitize_text_field((string) $rule['start_time']) : '00:00';
            $end_time = isset($rule['end_time']) ? sanitize_text_field((string) $rule['end_time']) : '23:59';
            if (!preg_match('/^\d{2}:\d{2}$/', $start_time)) {
                $start_time = '00:00';
            }
            if (!preg_match('/^\d{2}:\d{2}$/', $end_time)) {
                $end_time = '23:59';
            }
            $row['start_time'] = $start_time;
            $row['end_time'] = $end_time;
        }
        $normalized[] = $row;
    }

    if (!empty($normalized)) {
        return $normalized;
    }

    // Legacy fallback from simple blackout dates.
    $legacy_dates = (string) get_post_meta($post_id, 'chbk_blackout_dates', true);
    if ($legacy_dates !== '') {
        $parts = preg_split('/[\r\n,]+/', $legacy_dates);
        foreach ($parts as $part) {
            $date = trim((string) $part);
            if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
                continue;
            }
            $row = [
                'start_date' => $date,
                'end_date' => $date,
            ];
            if ($has_time) {
                $row['start_time'] = '00:00';
                $row['end_time'] = '23:59';
            }
            $normalized[] = $row;
        }
    }

    // Legacy fallback from captain unavailable spans.
    if ($has_time && get_post_type($post_id) === 'chbk_captain' && empty($normalized)) {
        $spans = chbk_parse_json_meta(get_post_meta($post_id, 'chbk_unavailable_spans', true));
        foreach ($spans as $span) {
            if (!is_array($span)) {
                continue;
            }
            $start_date = isset($span['start_date']) ? sanitize_text_field((string) $span['start_date']) : '';
            $end_date = isset($span['end_date']) ? sanitize_text_field((string) $span['end_date']) : '';
            if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $start_date) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $end_date)) {
                continue;
            }
            $start_time = isset($span['start_time']) ? sanitize_text_field((string) $span['start_time']) : '00:00';
            $end_time = isset($span['end_time']) ? sanitize_text_field((string) $span['end_time']) : '23:59';
            if (!preg_match('/^\d{2}:\d{2}$/', $start_time)) {
                $start_time = '00:00';
            }
            if (!preg_match('/^\d{2}:\d{2}$/', $end_time)) {
                $end_time = '23:59';
            }
            $normalized[] = [
                'start_date' => $start_date,
                'end_date' => $end_date,
                'start_time' => $start_time,
                'end_time' => $end_time,
            ];
        }
    }

    return $normalized;
}

function chbk_render_blackout_rules_ui($rules, $has_time, $label)
{
    if (empty($rules)) {
        $rules = [[
            'start_date' => '',
            'end_date' => '',
            'start_time' => '00:00',
            'end_time' => '23:59',
        ]];
    }

    echo '<div class="chbk-blackout-rules" data-has-time="' . ($has_time ? '1' : '0') . '">';
    echo '<h4>' . esc_html($label) . '</h4>';
    echo '<table class="widefat"><thead><tr><th>Start Date</th>';
    if ($has_time) {
        echo '<th>Start Time</th>';
    }
    echo '<th>End Date</th>';
    if ($has_time) {
        echo '<th>End Time</th>';
    }
    echo '<th></th></tr></thead><tbody class="chbk-blackout-rules-rows">';

    foreach ($rules as $rule) {
        $start_date = isset($rule['start_date']) ? (string) $rule['start_date'] : '';
        $end_date = isset($rule['end_date']) ? (string) $rule['end_date'] : '';
        $start_time = isset($rule['start_time']) ? (string) $rule['start_time'] : '00:00';
        $end_time = isset($rule['end_time']) ? (string) $rule['end_time'] : '23:59';

        echo '<tr>';
        printf('<td><input type="text" class="chbk-datepicker" name="chbk_blackout_start_date[]" value="%s"></td>', esc_attr($start_date));
        if ($has_time) {
            printf('<td><input type="time" name="chbk_blackout_start_time[]" value="%s"></td>', esc_attr($start_time));
        }
        printf('<td><input type="text" class="chbk-datepicker" name="chbk_blackout_end_date[]" value="%s"></td>', esc_attr($end_date));
        if ($has_time) {
            printf('<td><input type="time" name="chbk_blackout_end_time[]" value="%s"></td>', esc_attr($end_time));
        }
        echo '<td><button type="button" class="button chbk-blackout-rules-remove">Remove</button></td>';
        echo '</tr>';
    }

    echo '</tbody></table>';
    echo '<p><button type="button" class="button chbk-blackout-rules-add">Add Blackout Span</button></p>';
    echo '</div>';
}

function chbk_render_effective_blackout_preview($trip_id)
{
    $trip_rules = chbk_get_blackout_rules_for_post($trip_id, false);
    $captain_id = (int) get_post_meta($trip_id, 'chbk_captain_id', true);
    $boat_id = (int) get_post_meta($trip_id, 'chbk_boat_id', true);
    $captain_name = $captain_id ? get_the_title($captain_id) : '';
    $boat_name = $boat_id ? get_the_title($boat_id) : '';
    $captain_edit_url = $captain_id ? get_edit_post_link($captain_id) : '';
    $boat_edit_url = $boat_id ? get_edit_post_link($boat_id) : '';
    $captain_rules = chbk_get_blackout_rules_for_post($captain_id, true);
    $boat_rules = chbk_get_blackout_rules_for_post($boat_id, true);

    $rows = [];

    foreach ($trip_rules as $rule) {
        $rows[] = [
            'start_date' => isset($rule['start_date']) ? (string) $rule['start_date'] : '',
            'start_time' => '00:00',
            'end_date' => isset($rule['end_date']) ? (string) $rule['end_date'] : '',
            'end_time' => '23:59',
            'source' => 'Trip',
            'is_inherited' => false,
        ];
    }

    foreach ($captain_rules as $rule) {
        $rows[] = [
            'start_date' => isset($rule['start_date']) ? (string) $rule['start_date'] : '',
            'start_time' => isset($rule['start_time']) ? (string) $rule['start_time'] : '00:00',
            'end_date' => isset($rule['end_date']) ? (string) $rule['end_date'] : '',
            'end_time' => isset($rule['end_time']) ? (string) $rule['end_time'] : '23:59',
            'source' => 'Captain',
            'source_name' => $captain_name,
            'source_url' => $captain_edit_url,
            'is_inherited' => true,
        ];
    }

    foreach ($boat_rules as $rule) {
        $rows[] = [
            'start_date' => isset($rule['start_date']) ? (string) $rule['start_date'] : '',
            'start_time' => isset($rule['start_time']) ? (string) $rule['start_time'] : '00:00',
            'end_date' => isset($rule['end_date']) ? (string) $rule['end_date'] : '',
            'end_time' => isset($rule['end_time']) ? (string) $rule['end_time'] : '23:59',
            'source' => 'Boat',
            'source_name' => $boat_name,
            'source_url' => $boat_edit_url,
            'is_inherited' => true,
        ];
    }

    if (empty($rows)) {
        return;
    }

    usort($rows, function ($a, $b) {
        $a_key = ($a['start_date'] ?? '') . ' ' . ($a['start_time'] ?? '');
        $b_key = ($b['start_date'] ?? '') . ' ' . ($b['start_time'] ?? '');
        return strcmp($a_key, $b_key);
    });

    echo '<h4>Effective Blackout Spans</h4>';
    echo '<p class="description">Includes trip blackouts plus inherited captain/boat blackouts. Inherited rows are read-only.</p>';
    echo '<table class="widefat chbk-effective-blackouts"><thead><tr><th>Start Date</th><th>Start Time</th><th>End Date</th><th>End Time</th><th>Source</th></tr></thead><tbody>';

    foreach ($rows as $row) {
        $row_class = $row['is_inherited'] ? 'chbk-blackout-inherited' : '';
        echo '<tr' . ($row_class !== '' ? ' class="' . esc_attr($row_class) . '"' : '') . '>';
        echo '<td>' . esc_html($row['start_date']) . '</td>';
        echo '<td>' . esc_html($row['start_time']) . '</td>';
        echo '<td>' . esc_html($row['end_date']) . '</td>';
        echo '<td>' . esc_html($row['end_time']) . '</td>';
        $source_label = $row['source'];
        if (!empty($row['source_name']) && in_array($row['source'], ['Captain', 'Boat'], true)) {
            $source_label .= ' - ' . $row['source_name'];
        }
        $source_class = 'chbk-blackout-source chbk-blackout-source-' . strtolower((string) $row['source']);
        if (!empty($row['source_url']) && in_array($row['source'], ['Captain', 'Boat'], true)) {
            echo '<td><a class="' . esc_attr($source_class) . '" href="' . esc_url($row['source_url']) . '">' . esc_html($source_label) . '</a></td>';
        } else {
            echo '<td><span class="' . esc_attr($source_class) . '">' . esc_html($source_label) . '</span></td>';
        }
        echo '</tr>';
    }

    echo '</tbody></table>';
}

function chbk_save_blackout_rules_from_request($post_id, $has_time, $posted = null)
{
    if (!is_array($posted)) {
        $posted = wp_unslash($_POST);
    }

    if (!isset($posted['chbk_meta_nonce']) || !wp_verify_nonce(sanitize_text_field((string) $posted['chbk_meta_nonce']), 'chbk_save_meta')) {
        return;
    }

    $start_dates_raw = isset($posted['chbk_blackout_start_date']) && is_array($posted['chbk_blackout_start_date']) ? $posted['chbk_blackout_start_date'] : [];
    $end_dates_raw = isset($posted['chbk_blackout_end_date']) && is_array($posted['chbk_blackout_end_date']) ? $posted['chbk_blackout_end_date'] : [];
    $start_times_raw = isset($posted['chbk_blackout_start_time']) && is_array($posted['chbk_blackout_start_time']) ? $posted['chbk_blackout_start_time'] : [];
    $end_times_raw = isset($posted['chbk_blackout_end_time']) && is_array($posted['chbk_blackout_end_time']) ? $posted['chbk_blackout_end_time'] : [];

    $start_dates = array_map('sanitize_text_field', $start_dates_raw);
    $end_dates = array_map('sanitize_text_field', $end_dates_raw);
    $start_times = array_map('sanitize_text_field', $start_times_raw);
    $end_times = array_map('sanitize_text_field', $end_times_raw);

    $rules = [];
    $count = max(count($start_dates), count($end_dates));
    for ($i = 0; $i < $count; $i++) {
        $start_date = isset($start_dates[$i]) ? sanitize_text_field((string) $start_dates[$i]) : '';
        $end_date = isset($end_dates[$i]) ? sanitize_text_field((string) $end_dates[$i]) : '';
        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $start_date) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $end_date)) {
            continue;
        }
        if ($end_date < $start_date) {
            $tmp = $start_date;
            $start_date = $end_date;
            $end_date = $tmp;
        }

        $row = [
            'start_date' => $start_date,
            'end_date' => $end_date,
        ];

        if ($has_time) {
            $start_time = isset($start_times[$i]) ? sanitize_text_field((string) $start_times[$i]) : '00:00';
            $end_time = isset($end_times[$i]) ? sanitize_text_field((string) $end_times[$i]) : '23:59';
            if (!preg_match('/^\d{2}:\d{2}$/', $start_time)) {
                $start_time = '00:00';
            }
            if (!preg_match('/^\d{2}:\d{2}$/', $end_time)) {
                $end_time = '23:59';
            }
            $row['start_time'] = $start_time;
            $row['end_time'] = $end_time;
        }
        $rules[] = $row;
    }

    update_post_meta($post_id, 'chbk_blackout_rules', wp_json_encode($rules));

    // Keep legacy date list for backward compatibility with previous versions.
    $legacy_dates = [];
    foreach ($rules as $rule) {
        $legacy_dates[] = $rule['start_date'];
        $legacy_dates[] = $rule['end_date'];
    }
    $legacy_dates = array_values(array_unique($legacy_dates));
    sort($legacy_dates);
    update_post_meta($post_id, 'chbk_blackout_dates', implode("\n", $legacy_dates));
}

function chbk_render_captain_meta_box($post)
{
    wp_nonce_field('chbk_save_meta', 'chbk_meta_nonce');
    $user_id = (int) get_post_meta($post->ID, 'chbk_user_id', true);
    $status = get_post_meta($post->ID, 'chbk_status', true) ?: 'active';
    $users = get_users(['fields' => ['ID', 'display_name']]);

    echo '<p><label>User</label><br><select name="chbk_user_id"><option value="">Select User</option>';
    foreach ($users as $user) {
        printf('<option value="%d" %s>%s</option>', (int) $user->ID, selected($user_id, $user->ID, false), esc_html($user->display_name));
    }
    echo '</select></p>';

    echo '<p><label>Status</label><br><select name="chbk_status">';
    printf('<option value="active" %s>Active</option>', selected($status, 'active', false));
    printf('<option value="inactive" %s>Inactive</option>', selected($status, 'inactive', false));
    echo '</select></p>';
    chbk_render_blackout_rules_ui(
        chbk_get_blackout_rules_for_post($post->ID, true),
        true,
        'Captain Blackout Spans'
    );
}

function chbk_render_boat_meta_box($post)
{
    wp_nonce_field('chbk_save_meta', 'chbk_meta_nonce');
    $status = (string) get_post_meta($post->ID, 'chbk_status', true);
    if (!in_array($status, ['active', 'inactive'], true)) {
        $status = 'active';
    }
    $max_capacity = (int) get_post_meta($post->ID, 'chbk_max_capacity', true);
    $min_capacity = (int) get_post_meta($post->ID, 'chbk_min_capacity', true);
    $allows_overbooking = (int) get_post_meta($post->ID, 'chbk_allows_overbooking', true);

    printf('<p><label>Max Capacity</label><br><input type="number" name="chbk_max_capacity" value="%s"></p>', esc_attr((string) $max_capacity));
    printf('<p><label>Min Capacity</label><br><input type="number" name="chbk_min_capacity" value="%s"></p>', esc_attr((string) $min_capacity));
    echo '<p><label>Status</label><br><select name="chbk_status">';
    printf('<option value="active" %s>Active</option>', selected($status, 'active', false));
    printf('<option value="inactive" %s>Inactive</option>', selected($status, 'inactive', false));
    echo '</select></p>';
    printf('<p><label><input type="checkbox" name="chbk_allows_overbooking" value="1" %s> Allow overbooking</label></p>', checked($allows_overbooking, 1, false));

    chbk_render_blackout_rules_ui(
        chbk_get_blackout_rules_for_post($post->ID, true),
        true,
        'Boat Blackout Spans'
    );
}

function chbk_render_trip_meta_box($post)
{
    wp_nonce_field('chbk_save_meta', 'chbk_meta_nonce');

    $fields = [
        'captain_id', 'boat_id', 'trip_timezone_override', 'trip_type', 'base_price', 'seat_price',
        'included_seats_enabled', 'included_seats_count', 'active_status', 'schedule_weekdays',
        'schedule_start_time', 'schedule_duration_minutes',
        'single_booking_per_slot', 'children_not_allowed', 'season_start_date',
        'season_end_date', 'booking_window_days', 'payment_message_override',
    ];

    $values = [];
    foreach ($fields as $field) {
        $values[$field] = get_post_meta($post->ID, 'chbk_' . $field, true);
    }

    $values['schedule_start_time'] = $values['schedule_start_time'] ?: '08:00';
    $values['schedule_duration_minutes'] = (int) ($values['schedule_duration_minutes'] ?: 240);
    $values['single_booking_per_slot'] = (int) ($values['single_booking_per_slot'] === '' ? 1 : $values['single_booking_per_slot']);
    $values['booking_window_days'] = (int) ($values['booking_window_days'] ?: 90);
    $values['included_seats_enabled'] = (int) ($values['included_seats_enabled'] ?: 0);
    $values['included_seats_count'] = (int) ($values['included_seats_count'] ?: 0);
    $values['children_not_allowed'] = (int) ($values['children_not_allowed'] ?: 0);

    $weekdays = chbk_parse_weekdays_csv($values['schedule_weekdays']);
    if (empty($weekdays)) {
        $weekdays = [0, 1, 2, 3, 4, 5, 6];
    }

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

    echo '<div class="chbk-trip-assignment-row">';
    echo '<p><label>Captain</label><br><select name="chbk_captain_id"><option value="">Select Captain</option>';
    foreach ($captains as $captain) {
        printf('<option value="%d" %s>%s</option>', (int) $captain->ID, selected((int) $values['captain_id'], $captain->ID, false), esc_html($captain->post_title));
    }
    echo '</select></p>';

    echo '<p><label>Boat</label><br><select name="chbk_boat_id"><option value="">Select Boat</option>';
    foreach ($boats as $boat) {
        printf('<option value="%d" %s>%s</option>', (int) $boat->ID, selected((int) $values['boat_id'], $boat->ID, false), esc_html($boat->post_title));
    }
    echo '</select></p>';
    echo '</div>';
    echo '<hr class="chbk-trip-section-divider">';

    $trip_type_value = (string) $values['trip_type'];
    if ($trip_type_value === '' || $trip_type_value === 'private') {
        $trip_type_value = 'per-group';
    }
    if (!in_array($trip_type_value, ['per-group', 'per-seat', 'hybrid'], true)) {
        $trip_type_value = 'per-group';
    }

    echo '<p><label>Trip Pricing Type ';
    echo '<span class="dashicons dashicons-editor-help" title="Per-group: one flat price for the whole booking. Per-seat: only Seat Price is used for calculation. Hybrid: Base Price plus Seat Price for each passenger above included seats."></span>';
    echo '</label><br><select name="chbk_trip_type" class="regular-text">';
    printf('<option value="per-group" %s>Per Group</option>', selected($trip_type_value, 'per-group', false));
    printf('<option value="per-seat" %s>Per Seat</option>', selected($trip_type_value, 'per-seat', false));
    printf('<option value="hybrid" %s>Hybrid</option>', selected($trip_type_value, 'hybrid', false));
    echo '</select></p>';
    echo '<div class="chbk-trip-pricing-row">';
    echo '<p class="chbk-base-price-row"><label>Base Price</label><br><input type="number" step="0.01" name="chbk_base_price" value="' . esc_attr((string) $values['base_price']) . '"></p>';
    echo '<p class="chbk-seat-price-row"><label>Seat Price</label><br><input type="number" step="0.01" min="0" name="chbk_seat_price" value="' . esc_attr((string) $values['seat_price']) . '"></p>';
    echo '</div>';
    echo '<p class="chbk-included-seats-wrap"><label><input type="checkbox" name="chbk_included_seats_enabled" value="1" ' . checked((int) $values['included_seats_enabled'], 1, false) . '> first <input type="number" min="0" name="chbk_included_seats_count" value="' . esc_attr((string) $values['included_seats_count']) . '" style="width:80px"> seats included in base price</label></p>';
    do_action('chbk_render_trip_meta_online_payment_fields', $post->ID);
    do_action('chbk_render_trip_meta_max_party_fields', $post->ID);
    echo '<p><label><input type="checkbox" name="chbk_children_not_allowed" value="1" ' . checked((int) $values['children_not_allowed'], 1, false) . '> children are not allowed</label></p>';

    echo '<hr><h4>Availability Rules</h4>';
    echo '<p><strong>Operating Days</strong></p>';
    $weekday_labels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
    echo '<div class="chbk-operating-days">';
    foreach ($weekday_labels as $i => $label) {
        $is_checked = in_array($i, $weekdays, true);
        $checked_class = $is_checked ? ' is-checked' : '';
        printf('<label class="chbk-day-pill%s"><input type="checkbox" name="chbk_schedule_weekdays[]" value="%d" %s><span>%s</span></label>', esc_attr($checked_class), (int) $i, checked($is_checked, true, false), esc_html($label));
    }
    echo '</div>';

    echo '<p><strong>Trip Instance Schedule</strong></p>';
    echo '<div class="chbk-trip-time-row">';
    printf('<p><label>Start Time <input type="time" name="chbk_schedule_start_time" value="%s"></label></p>', esc_attr($values['schedule_start_time']));
    printf('<p><label>Duration (minutes) <input type="number" min="1" name="chbk_schedule_duration_minutes" value="%d"></label></p>', (int) $values['schedule_duration_minutes']);
    echo '</div>';

    printf('<p><label><input type="checkbox" name="chbk_single_booking_per_slot" value="1" %s> Lock slot after first booking (including pending payment)</label></p>', checked($values['single_booking_per_slot'], 1, false));
    echo '<p><label>Timezone Override</label><br>';
    echo '<select name="chbk_trip_timezone_override">';
    $site_tz = chbk_get_default_site_timezone();
    printf('<option value="">WordPress Site Timezone (%s)</option>', esc_html($site_tz));
    $timezone_choices = wp_timezone_choice((string) $values['trip_timezone_override']);
    $timezone_choices = str_replace('Select a city', 'Use site default', (string) $timezone_choices);
    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_timezone_choice() returns trusted core-generated <option> markup.
    echo $timezone_choices;
    echo '</select>';
    echo '</p>';

    echo '<hr class="chbk-trip-section-divider">';
    echo '<p><strong>Season Window (optional)</strong></p>';
    echo '<div class="chbk-trip-season-row">';
    printf('<p><label>Season Start <input type="text" class="chbk-datepicker" name="chbk_season_start_date" value="%s" placeholder="YYYY-MM-DD"></label></p>', esc_attr($values['season_start_date']));
    printf('<p><label>Season End <input type="text" class="chbk-datepicker" name="chbk_season_end_date" value="%s" placeholder="YYYY-MM-DD"></label></p>', esc_attr($values['season_end_date']));
    echo '</div>';
    echo '<hr class="chbk-trip-section-divider">';

    chbk_render_blackout_rules_ui(
        chbk_get_blackout_rules_for_post($post->ID, false),
        false,
        'Trip Blackout Date Spans'
    );
    chbk_render_effective_blackout_preview($post->ID);
    printf('<p><label>How many days ahead to open bookings <input type="number" min="1" max="365" name="chbk_booking_window_days" value="%d"></label></p>', (int) $values['booking_window_days']);
    echo '<hr class="chbk-trip-section-divider">';
    $payment_message_placeholder = (string) chbk_get_setting('payment_message', '');
    if (trim($payment_message_placeholder) === '') {
        $defaults = chbk_get_default_settings();
        $payment_message_placeholder = isset($defaults['payment_message']) ? (string) $defaults['payment_message'] : '';
    }
    printf('<p><label>Payment Message Override (optional)</label><br><textarea name="chbk_payment_message_override" class="large-text" rows="3" placeholder="%s">%s</textarea><br><span class="description">Available variable: <code>{{phone_number}}</code>.</span></p>', esc_attr($payment_message_placeholder), esc_textarea((string) $values['payment_message_override']));
    echo '<hr class="chbk-trip-section-divider">';

    $active_status = $values['active_status'] ?: 'active';
    echo '<p><label>Status</label><br><select name="chbk_active_status">';
    printf('<option value="active" %s>Active</option>', selected($active_status, 'active', false));
    printf('<option value="inactive" %s>Inactive</option>', selected($active_status, 'inactive', false));
    echo '</select></p>';

}

function chbk_render_booking_meta_box($post)
{
    wp_nonce_field('chbk_save_meta', 'chbk_meta_nonce');
    $keys = [
        'trip_id', 'boat_id', 'captain_id', 'user_id', 'party_size', 'booking_status',
        'payment_status', 'order_id', 'checkin_status', 'trip_datetime_utc', 'total_price',
        'deposit_due', 'customer_name', 'customer_email',
    ];
    $values = [];
    foreach ($keys as $key) {
        $values[$key] = get_post_meta($post->ID, 'chbk_' . $key, true);
    }

    $trip_id = (int) $values['trip_id'];
    $boat_id = (int) $values['boat_id'];
    $captain_id = (int) $values['captain_id'];
    $trip = $trip_id ? get_post($trip_id) : null;
    $boat = $boat_id ? get_post($boat_id) : null;
    $captain = $captain_id ? get_post($captain_id) : null;

    $booking_status = (string) ($values['booking_status'] ?: 'pending');
    $payment_status = (string) ($values['payment_status'] ?: 'pending_payment');
    $checkin_status = (string) ($values['checkin_status'] ?: 'not_checked_in');
    $party_size = (int) ($values['party_size'] ?: 1);
    $order_id = (int) ($values['order_id'] ?: 0);
    $total_price = $values['total_price'] === '' ? '0' : (string) $values['total_price'];
    $deposit_due = $values['deposit_due'] === '' ? '0' : (string) $values['deposit_due'];
    $trip_datetime_utc = (string) $values['trip_datetime_utc'];

    $trip_timezone = function_exists('chbk_get_trip_timezone')
        ? chbk_get_trip_timezone($trip_id)
        : chbk_get_default_site_timezone();
    $trip_datetime_local_label = 'Not set';
    if ($trip_datetime_utc !== '') {
        $trip_datetime_local = chbk_from_utc_datetime($trip_datetime_utc, $trip_timezone);
        if ($trip_datetime_local !== '') {
            try {
                $dt_local = new DateTime($trip_datetime_local, new DateTimeZone($trip_timezone));
                $trip_datetime_local_label = $dt_local->format('M j, Y g:i a T');
            } catch (Exception $e) {
                $trip_datetime_local_label = $trip_datetime_local;
            }
        } else {
            $trip_datetime_local_label = $trip_datetime_utc;
        }
    }

    $assignment_slots = [];
    if ($trip_id > 0 && function_exists('chbk_get_trip_available_slots')) {
        $window_days = (int) get_post_meta($trip_id, 'chbk_booking_window_days', true);
        $assignment_slots = chbk_get_trip_available_slots($trip_id, max(1, $party_size), $window_days > 0 ? $window_days : 90);
    }
    $has_current_slot = false;
    foreach ($assignment_slots as $slot) {
        if (isset($slot['utc']) && (string) $slot['utc'] === $trip_datetime_utc) {
            $has_current_slot = true;
            break;
        }
    }
    if (!$has_current_slot && $trip_datetime_utc !== '') {
        $current_local = chbk_from_utc_datetime($trip_datetime_utc, $trip_timezone);
        $current_label = $trip_datetime_local_label;
        if ($current_local !== '') {
            $current_label .= ' (Current)';
        }
        $assignment_slots = array_merge([[
            'local' => $current_local,
            'utc' => $trip_datetime_utc,
            'label' => $current_label,
        ]], $assignment_slots);
    }

    $is_wc_addon_active = defined('CHBK_WC_VERSION');
    $captain_options = get_posts(['post_type' => 'chbk_captain', 'post_status' => 'publish', 'numberposts' => -1]);
    $boat_options = get_posts(['post_type' => 'chbk_boat', 'post_status' => 'publish', 'numberposts' => -1]);

    $currency = (string) chbk_get_setting('currency', 'USD');
    $status_options = chbk_booking_status_options($booking_status, 'booking');
    $payment_options = chbk_booking_status_options($payment_status, 'payment');
    $checkin_options = chbk_booking_status_options($checkin_status, 'checkin');

    echo '<div class="chbk-booking-admin">';
    echo '<div class="chbk-booking-header">';
    echo '<h3>Booking #' . (int) $post->ID . '</h3>';
    echo '<div class="chbk-booking-status-badges">';
    printf('<span class="chbk-booking-badge chbk-booking-badge-booking">%s</span>', esc_html(chbk_humanize_slug($booking_status)));
    printf('<span class="chbk-booking-badge chbk-booking-badge-payment">%s</span>', esc_html(chbk_humanize_slug($payment_status)));
    printf('<span class="chbk-booking-badge chbk-booking-badge-checkin">%s</span>', esc_html(chbk_humanize_slug($checkin_status)));
    echo '</div>';
    echo '</div>';

    echo '<div class="chbk-booking-grid">';

    echo '<section class="chbk-booking-card">';
    echo '<div class="chbk-booking-card-head"><h4>Trip & Assignment</h4><button type="button" class="button-link chbk-booking-edit-toggle" data-target="assignment">Edit</button></div>';
    echo '<div class="chbk-booking-card-body">';
    echo '<div class="chbk-booking-row"><span class="chbk-booking-label">Trip</span><span class="chbk-booking-value">';
    if ($trip && $trip->post_type === 'chbk_trip') {
        printf('<a href="%s">%s</a>', esc_url(get_edit_post_link($trip->ID)), esc_html($trip->post_title ?: ('Trip #' . $trip->ID)));
    } else {
        echo '<em>Not linked</em>';
    }
    echo '</span></div>';
    echo '<div class="chbk-booking-row"><span class="chbk-booking-label">Captain</span><span class="chbk-booking-value">';
    if ($captain && $captain->post_type === 'chbk_captain') {
        printf('<a href="%s">%s</a>', esc_url(get_edit_post_link($captain->ID)), esc_html($captain->post_title ?: ('Captain #' . $captain->ID)));
    } else {
        echo '<em>Not linked</em>';
    }
    echo '</span></div>';
    echo '<div class="chbk-booking-row"><span class="chbk-booking-label">Boat</span><span class="chbk-booking-value">';
    if ($boat && $boat->post_type === 'chbk_boat') {
        printf('<a href="%s">%s</a>', esc_url(get_edit_post_link($boat->ID)), esc_html($boat->post_title ?: ('Boat #' . $boat->ID)));
    } else {
        echo '<em>Not linked</em>';
    }
    echo '</span></div>';
    printf('<div class="chbk-booking-row"><span class="chbk-booking-label">Party Size</span><span class="chbk-booking-value">%d</span></div>', (int) $party_size);
    echo '<div class="chbk-booking-row"><span class="chbk-booking-label">Trip Date/Time</span><span class="chbk-booking-value">' . esc_html($trip_datetime_local_label) . '</span></div>';
    echo '<div class="chbk-booking-row"><span class="chbk-booking-label">Created</span><span class="chbk-booking-value">' . esc_html(get_the_date('M j, Y g:i a', $post)) . '</span></div>';
    echo '<div class="chbk-booking-edit chbk-booking-edit-assignment">';
    printf('<p><label>Party Size<br><input type="number" min="1" class="small-text" name="chbk_party_size" value="%d"></label></p>', (int) $party_size);
    echo '<p><label>Trip Date/Time<br><select name="chbk_trip_datetime_utc" class="regular-text">';
    if (!empty($assignment_slots)) {
        foreach ($assignment_slots as $slot) {
            $slot_utc = isset($slot['utc']) ? (string) $slot['utc'] : '';
            $slot_label = isset($slot['label']) ? (string) $slot['label'] : $slot_utc;
            printf('<option value="%s" %s>%s</option>', esc_attr($slot_utc), selected($trip_datetime_utc, $slot_utc, false), esc_html($slot_label));
        }
    } else {
        echo '<option value="">No open slots available</option>';
    }
    echo '</select></label></p>';
    echo '<p><label>Captain<br><select name="chbk_captain_id" class="regular-text">';
    echo '<option value="">Select Captain</option>';
    foreach ($captain_options as $captain_option) {
        printf('<option value="%d" %s>%s</option>', (int) $captain_option->ID, selected($captain_id, (int) $captain_option->ID, false), esc_html($captain_option->post_title));
    }
    echo '</select></label></p>';
    echo '<p><label>Boat<br><select name="chbk_boat_id" class="regular-text">';
    echo '<option value="">Select Boat</option>';
    foreach ($boat_options as $boat_option) {
        printf('<option value="%d" %s>%s</option>', (int) $boat_option->ID, selected($boat_id, (int) $boat_option->ID, false), esc_html($boat_option->post_title));
    }
    echo '</select></label></p>';
    echo '</div>';
    echo '</div>';
    echo '</section>';

    echo '<section class="chbk-booking-card">';
    echo '<div class="chbk-booking-card-head"><h4>Customer</h4><button type="button" class="button-link chbk-booking-edit-toggle" data-target="customer">Edit</button></div>';
    echo '<div class="chbk-booking-card-body">';
    echo '<div class="chbk-booking-row"><span class="chbk-booking-label">Name</span><span class="chbk-booking-value chbk-booking-view-customer">' . esc_html((string) $values['customer_name']) . '</span></div>';
    echo '<div class="chbk-booking-row"><span class="chbk-booking-label">Email</span><span class="chbk-booking-value chbk-booking-view-customer">' . esc_html((string) $values['customer_email']) . '</span></div>';
    echo '<div class="chbk-booking-edit chbk-booking-edit-customer">';
    printf('<p><label>Name<br><input type="text" class="regular-text" name="chbk_customer_name" value="%s"></label></p>', esc_attr((string) $values['customer_name']));
    printf('<p><label>Email<br><input type="email" class="regular-text" name="chbk_customer_email" value="%s"></label></p>', esc_attr((string) $values['customer_email']));
    echo '</div>';
    echo '</div>';
    echo '</section>';

    echo '<section class="chbk-booking-card">';
    echo '<div class="chbk-booking-card-head"><h4>Status & Operations</h4><button type="button" class="button-link chbk-booking-edit-toggle" data-target="status">Edit</button></div>';
    echo '<div class="chbk-booking-card-body">';
    printf('<div class="chbk-booking-row"><span class="chbk-booking-label">Booking Status</span><span class="chbk-booking-value">%s</span></div>', esc_html(chbk_humanize_slug($booking_status)));
    printf('<div class="chbk-booking-row"><span class="chbk-booking-label">Payment Status</span><span class="chbk-booking-value">%s</span></div>', esc_html(chbk_humanize_slug($payment_status)));
    printf('<div class="chbk-booking-row"><span class="chbk-booking-label">Check-in Status</span><span class="chbk-booking-value">%s</span></div>', esc_html(chbk_humanize_slug($checkin_status)));
    echo '<div class="chbk-booking-edit chbk-booking-edit-status">';
    echo '<p><label>Booking Status<br><select name="chbk_booking_status">';
    foreach ($status_options as $value => $label) {
        printf('<option value="%s" %s>%s</option>', esc_attr($value), selected($booking_status, $value, false), esc_html($label));
    }
    echo '</select></label></p>';
    echo '<p><label>Payment Status<br><select name="chbk_payment_status">';
    foreach ($payment_options as $value => $label) {
        printf('<option value="%s" %s>%s</option>', esc_attr($value), selected($payment_status, $value, false), esc_html($label));
    }
    echo '</select></label></p>';
    echo '<p><label>Check-in Status<br><select name="chbk_checkin_status">';
    foreach ($checkin_options as $value => $label) {
        printf('<option value="%s" %s>%s</option>', esc_attr($value), selected($checkin_status, $value, false), esc_html($label));
    }
    echo '</select></label></p>';
    echo '</div>';
    echo '</div>';
    echo '</section>';

    echo '<section class="chbk-booking-card">';
    echo '<div class="chbk-booking-card-head"><h4>Charges</h4><button type="button" class="button-link chbk-booking-edit-toggle" data-target="charges">Edit</button></div>';
    echo '<div class="chbk-booking-card-body">';
    printf('<div class="chbk-booking-row"><span class="chbk-booking-label">Total Price</span><span class="chbk-booking-value">%s %s</span></div>', esc_html(number_format((float) $total_price, 2, '.', '')), esc_html($currency));
    printf('<div class="chbk-booking-row"><span class="chbk-booking-label">Deposit Due</span><span class="chbk-booking-value">%s %s</span></div>', esc_html(number_format((float) $deposit_due, 2, '.', '')), esc_html($currency));
    if ($is_wc_addon_active) {
        echo '<div class="chbk-booking-row"><span class="chbk-booking-label">Woo Order</span><span class="chbk-booking-value">';
        if ($order_id > 0) {
            echo '#' . (int) $order_id;
        } else {
            echo '<em>None</em>';
        }
        echo '</span></div>';
    }
    echo '<div class="chbk-booking-edit chbk-booking-edit-charges">';
    printf('<p><label>Total Price<br><input type="number" step="0.01" min="0" class="small-text" name="chbk_total_price" value="%s"></label></p>', esc_attr($total_price));
    printf('<p><label>Deposit Due<br><input type="number" step="0.01" min="0" class="small-text" name="chbk_deposit_due" value="%s"></label></p>', esc_attr($deposit_due));
    if ($is_wc_addon_active) {
        printf('<p><label>Woo Order ID<br><input type="number" min="0" class="small-text" name="chbk_order_id" value="%d"></label></p>', (int) $order_id);
    }
    echo '</div>';
    echo '</div>';
    echo '</section>';

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

function chbk_humanize_slug($value)
{
    $value = trim((string) $value);
    if ($value === '') {
        return 'Not set';
    }
    return ucwords(str_replace(['_', '-'], ' ', $value));
}

function chbk_booking_status_options($current, $type)
{
    $sets = [
        'booking' => [
            'pending' => 'Pending',
            'confirmed' => 'Confirmed',
            'cancelled' => 'Cancelled',
            'completed' => 'Completed',
            'no_show' => 'No Show',
        ],
        'payment' => [
            'pending_payment' => 'Pending Payment',
            'partial' => 'Partial',
            'paid' => 'Paid',
            'refunded' => 'Refunded',
            'failed' => 'Failed',
        ],
        'checkin' => [
            'not_checked_in' => 'Not Checked In',
            'checked_in' => 'Checked In',
            'no_show' => 'No Show',
        ],
    ];

    $options = isset($sets[$type]) ? $sets[$type] : [];
    $current = trim((string) $current);
    if ($current !== '' && !isset($options[$current])) {
        $options = [$current => chbk_humanize_slug($current)] + $options;
    }
    return $options;
}

function chbk_parse_weekdays_csv($raw)
{
    if (is_array($raw)) {
        $items = $raw;
    } else {
        $raw = (string) $raw;
        $items = $raw === '' ? [] : preg_split('/[^0-6]+/', $raw);
    }

    $days = [];
    foreach ($items as $item) {
        if ($item === '' || $item === null) {
            continue;
        }
        $day = (int) $item;
        if ($day >= 0 && $day <= 6) {
            $days[] = $day;
        }
    }

    $days = array_values(array_unique($days));
    sort($days);
    return $days;
}

function chbk_sanitize_trip_extension_posted_values($posted)
{
    if (!is_array($posted)) {
        $posted = [];
    }

    $deposit_type = isset($posted['chbk_online_payment_deposit_type']) ? sanitize_key((string) $posted['chbk_online_payment_deposit_type']) : 'fixed';
    if (!in_array($deposit_type, ['fixed', 'percentage'], true)) {
        $deposit_type = 'fixed';
    }

    return [
        'chbk_online_payment_due_at_booking' => isset($posted['chbk_online_payment_due_at_booking']) ? '1' : '',
        'chbk_online_payment_must_pay_full' => isset($posted['chbk_online_payment_must_pay_full']) ? '1' : '',
        'chbk_online_payment_deposit_amount' => isset($posted['chbk_online_payment_deposit_amount']) ? (string) max(0, (float) $posted['chbk_online_payment_deposit_amount']) : '0',
        'chbk_online_payment_deposit_type' => $deposit_type,
        'chbk_max_party_size_enabled' => isset($posted['chbk_max_party_size_enabled']) ? '1' : '',
        'chbk_max_party_size_override' => isset($posted['chbk_max_party_size_override']) ? (string) absint($posted['chbk_max_party_size_override']) : '0',
    ];
}

add_action('save_post', 'chbk_save_meta_boxes');

function chbk_save_meta_boxes($post_id)
{
    if (!isset($_POST['chbk_meta_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['chbk_meta_nonce'])), 'chbk_save_meta')) {
        return;
    }

    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }

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

    $posted = wp_unslash($_POST);

    $post_type = get_post_type($post_id);

    if ($post_type === 'chbk_captain') {
        $fields = ['user_id', 'status'];
        foreach ($fields as $field) {
            $input_key = 'chbk_' . $field;
            $value = isset($posted[$input_key]) ? sanitize_text_field((string) $posted[$input_key]) : '';
            update_post_meta($post_id, $input_key, chbk_sanitize_meta_value($value));
        }
        chbk_save_blackout_rules_from_request($post_id, true, $posted);
        delete_post_meta($post_id, 'chbk_unavailable_spans');
        return;
    }

    if ($post_type === 'chbk_boat') {
        $fields = ['max_capacity', 'min_capacity'];
        foreach ($fields as $field) {
            $input_key = 'chbk_' . $field;
            $value = isset($posted[$input_key]) ? sanitize_text_field((string) $posted[$input_key]) : '';
            update_post_meta($post_id, $input_key, chbk_sanitize_meta_value($value));
        }

        $status = isset($posted['chbk_status']) ? sanitize_key((string) $posted['chbk_status']) : 'active';
        if (!in_array($status, ['active', 'inactive'], true)) {
            $status = 'active';
        }
        update_post_meta($post_id, 'chbk_status', $status);
        chbk_save_blackout_rules_from_request($post_id, true, $posted);
        update_post_meta($post_id, 'chbk_allows_overbooking', isset($posted['chbk_allows_overbooking']) ? 1 : 0);
        return;
    }

    if ($post_type === 'chbk_trip') {
        $fields = [
            'captain_id', 'boat_id', 'trip_timezone_override', 'trip_type', 'base_price', 'seat_price', 'active_status', 'schedule_start_time',
            'schedule_duration_minutes',
            'season_start_date', 'season_end_date', 'booking_window_days', 'payment_message_override',
        ];

        foreach ($fields as $field) {
            $input_key = 'chbk_' . $field;
            $value = isset($posted[$input_key]) ? (string) $posted[$input_key] : '';
            if ($field === 'trip_timezone_override') {
                $tz_value = sanitize_text_field($value);
                $valid = $tz_value === '' || in_array($tz_value, timezone_identifiers_list(), true);
                update_post_meta($post_id, $input_key, $valid ? $tz_value : '');
                continue;
            }
            update_post_meta($post_id, $input_key, chbk_sanitize_meta_value($value));
        }

        $weekdays = isset($posted['chbk_schedule_weekdays']) && is_array($posted['chbk_schedule_weekdays']) ? chbk_parse_weekdays_csv($posted['chbk_schedule_weekdays']) : [];
        update_post_meta($post_id, 'chbk_schedule_weekdays', implode(',', $weekdays));
        update_post_meta($post_id, 'chbk_single_booking_per_slot', isset($posted['chbk_single_booking_per_slot']) ? 1 : 0);
        update_post_meta($post_id, 'chbk_included_seats_enabled', isset($posted['chbk_included_seats_enabled']) ? 1 : 0);
        update_post_meta($post_id, 'chbk_included_seats_count', isset($posted['chbk_included_seats_count']) ? max(0, (int) $posted['chbk_included_seats_count']) : 0);
        update_post_meta($post_id, 'chbk_children_not_allowed', isset($posted['chbk_children_not_allowed']) ? 1 : 0);
        $trip_extension_posted = chbk_sanitize_trip_extension_posted_values($posted);
        do_action('chbk_save_trip_meta_extension_fields', $post_id, $trip_extension_posted);

        $trip_type = (string) get_post_meta($post_id, 'chbk_trip_type', true);
        if ($trip_type === 'private') {
            $trip_type = 'per-group';
        }
        if (!in_array($trip_type, ['per-group', 'per-seat', 'hybrid'], true)) {
            $trip_type = 'per-group';
        }
        update_post_meta($post_id, 'chbk_trip_type', $trip_type);
        if ($trip_type !== 'hybrid') {
            update_post_meta($post_id, 'chbk_included_seats_enabled', 0);
            update_post_meta($post_id, 'chbk_included_seats_count', 0);
        }
        chbk_save_blackout_rules_from_request($post_id, false, $posted);

        $duration_minutes = (int) get_post_meta($post_id, 'chbk_schedule_duration_minutes', true);
        if ($duration_minutes < 1) {
            $duration_minutes = 240;
            update_post_meta($post_id, 'chbk_schedule_duration_minutes', $duration_minutes);
        }

        $start_time = (string) get_post_meta($post_id, 'chbk_schedule_start_time', true);
        if ($start_time === '') {
            $start_time = '08:00';
            update_post_meta($post_id, 'chbk_schedule_start_time', $start_time);
        }

        // Backward-compat for prior slot-window model.
        update_post_meta($post_id, 'chbk_schedule_window_start', $start_time);
        update_post_meta($post_id, 'chbk_schedule_window_end', '23:59');
        update_post_meta($post_id, 'chbk_schedule_buffer_minutes', 0);

        update_post_meta($post_id, 'chbk_duration', sprintf('%d minutes', $duration_minutes));

        return;
    }

    if ($post_type === 'chbk_booking') {
        $trip_id = (int) get_post_meta($post_id, 'chbk_trip_id', true);
        $old_payment_status = (string) get_post_meta($post_id, 'chbk_payment_status', true);
        $new_payment_status = $old_payment_status;
        $fields = [
            'party_size', 'booking_status', 'payment_status', 'order_id', 'checkin_status',
            'trip_datetime_utc', 'total_price', 'deposit_due', 'customer_name', 'customer_email',
        ];

        foreach ($fields as $field) {
            $input_key = 'chbk_' . $field;
            if (!isset($posted[$input_key])) {
                continue;
            }
            $value = $posted[$input_key];
            if ($field === 'trip_datetime_utc') {
                $value = sanitize_text_field((string) $value);
                if (trim($value) === '') {
                    continue;
                }
                $party_size = isset($posted['chbk_party_size']) ? max(1, (int) $posted['chbk_party_size']) : (int) get_post_meta($post_id, 'chbk_party_size', true);
                $existing_utc = (string) get_post_meta($post_id, 'chbk_trip_datetime_utc', true);
                if ($trip_id > 0 && $value !== $existing_utc && function_exists('chbk_is_trip_available') && !chbk_is_trip_available($trip_id, $party_size, $value)) {
                    continue;
                }
            }
            update_post_meta($post_id, $input_key, chbk_sanitize_meta_value($value));
            if ($field === 'payment_status') {
                $new_payment_status = sanitize_text_field((string) $value);
            }
        }

        if (isset($posted['chbk_captain_id'])) {
            $captain_id = (int) $posted['chbk_captain_id'];
            if ($captain_id > 0 && get_post_type($captain_id) !== 'chbk_captain') {
                $captain_id = 0;
            }
            update_post_meta($post_id, 'chbk_captain_id', $captain_id);
        }
        if (isset($posted['chbk_boat_id'])) {
            $boat_id = (int) $posted['chbk_boat_id'];
            if ($boat_id > 0 && get_post_type($boat_id) !== 'chbk_boat') {
                $boat_id = 0;
            }
            update_post_meta($post_id, 'chbk_boat_id', $boat_id);
        }

        if ($new_payment_status !== $old_payment_status && in_array($new_payment_status, ['paid_full', 'paid_deposit', 'partial'], true) && function_exists('chbk_append_payment_ledger_entry')) {
            $total = (float) get_post_meta($post_id, 'chbk_total_price', true);
            $deposit = (float) get_post_meta($post_id, 'chbk_deposit_due', true);
            $amount = $new_payment_status === 'paid_full' ? $total : ($deposit > 0 ? $deposit : $total);
            chbk_append_payment_ledger_entry($post_id, [
                'amount' => $amount,
                'method' => 'manual',
                'source' => 'admin',
                'note' => 'Payment status updated in booking editor',
            ]);
        }
    }
}
