<?php
require_once __DIR__ . '/../../includes/bootstrap.php';
require_login();

$pageTitle = 'Reservations';

$waitlistSupport = __DIR__ . '/waitlist_support.php';
if (file_exists($waitlistSupport)) {
    require_once $waitlistSupport;
}

if (!function_exists('h')) {
    function h($value): string
    {
        return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
    }
}

if (!function_exists('display_money_local')) {
    function display_money_local($amount): string
    {
        return number_format((float)$amount, 2);
    }
}

if (!function_exists('display_date_local')) {
    function display_date_local($value, $format = 'd M Y'): string
    {
        if (!$value) {
            return '';
        }
        $ts = strtotime((string)$value);
        return $ts ? date($format, $ts) : (string)$value;
    }
}

if (!function_exists('status_badge_meta')) {
    function status_badge_meta($status): array
    {
        switch ((string)$status) {
            case '1': return ['Confirmed', 'success'];
            case '2': return ['Provisional', 'warning'];
            case '3': return ['In House', 'primary'];
            case '4': return ['Checked Out', 'secondary'];
            case '0': return ['Cancelled', 'danger'];
            case 'no_show':
            case 'noshow': return ['No Show', 'danger'];
            default:  return ['Open', 'dark'];
        }
    }
}

if (!function_exists('payment_badge_meta')) {
    function payment_badge_meta($status): array
    {
        switch ((string)$status) {
            case '2': return ['Paid', 'success'];
            case '1': return ['Part Paid', 'warning'];
            case '0':
            default:  return ['Unpaid', 'danger'];
        }
    }
}


if (!function_exists('reservation_frontdesk_status_value')) {
    function reservation_frontdesk_status_value(array $row): string
    {
        $storedStatus = (string)($row['status'] ?? '');
        $bookingStatus = strtolower(trim((string)($row['booking_status'] ?? '')));

        if (in_array($bookingStatus, ['no_show', 'noshow'], true) || !empty($row['no_show_at'])) {
            return 'no_show';
        }

        if ($storedStatus === '0') {
            return '0';
        }

        $slotsTotal = (int)($row['room_slots_total'] ?? 0);
        $checkedIn = (int)($row['room_slots_checked_in'] ?? 0);
        $checkedOut = (int)($row['room_slots_checked_out'] ?? 0);

        if ($slotsTotal > 0 && $checkedOut >= $slotsTotal) {
            return '4';
        }

        if ($checkedIn > 0) {
            return '3';
        }

        return $storedStatus !== '' ? $storedStatus : '1';
    }
}

if (!function_exists('reservation_frontdesk_status_note')) {
    function reservation_frontdesk_status_note(array $row): string
    {
        $slotsTotal = (int)($row['room_slots_total'] ?? 0);
        if ($slotsTotal <= 0) {
            return '';
        }

        $checkedIn = (int)($row['room_slots_checked_in'] ?? 0);
        $checkedOut = (int)($row['room_slots_checked_out'] ?? 0);

        return 'In ' . $checkedIn . '/' . $slotsTotal . ' • Out ' . $checkedOut . '/' . $slotsTotal;
    }
}

if (!function_exists('rl_column_exists')) {
    function rl_column_exists(PDO $pdo, string $table, string $column): bool
    {
        static $cache = [];
        $key = $table . '.' . $column;

        if (array_key_exists($key, $cache)) {
            return $cache[$key];
        }

        try {
            $stmt = $pdo->prepare("
                SELECT COUNT(*)
                FROM INFORMATION_SCHEMA.COLUMNS
                WHERE TABLE_SCHEMA = DATABASE()
                  AND TABLE_NAME = :table_name
                  AND COLUMN_NAME = :column_name
            ");
            $stmt->execute([
                ':table_name'  => $table,
                ':column_name' => $column,
            ]);
            $cache[$key] = ((int)$stmt->fetchColumn() > 0);
        } catch (Throwable $e) {
            $cache[$key] = false;
        }

        return $cache[$key];
    }
}


if (!function_exists('rl_table_exists')) {
    function rl_table_exists(PDO $pdo, string $table): bool
    {
        static $cache = [];
        if (array_key_exists($table, $cache)) {
            return $cache[$table];
        }

        try {
            $stmt = $pdo->prepare("\n                SELECT COUNT(*)\n                FROM INFORMATION_SCHEMA.TABLES\n                WHERE TABLE_SCHEMA = DATABASE()\n                  AND TABLE_NAME = :table_name\n            ");
            $stmt->execute([':table_name' => $table]);
            $cache[$table] = ((int)$stmt->fetchColumn() > 0);
        } catch (Throwable $e) {
            $cache[$table] = false;
        }

        return $cache[$table];
    }
}


if (!function_exists('rl_try_add_column')) {
    function rl_try_add_column(PDO $pdo, string $table, string $column, string $definition): void
    {
        if (rl_column_exists($pdo, $table, $column)) return;
        try {
            $pdo->exec("ALTER TABLE `" . str_replace('`', '``', $table) . "` ADD COLUMN " . $definition);
        } catch (Throwable $e) {
            // Keep page operational if ALTER permission is blocked.
        }
    }
}

if (!function_exists('rl_current_user_id')) {
    function rl_current_user_id(): ?int
    {
        foreach (['user_id', 'ss_user_id', 'id', 'uid'] as $key) {
            if (isset($_SESSION[$key]) && (int)$_SESSION[$key] > 0) return (int)$_SESSION[$key];
        }
        return null;
    }
}

if (!function_exists('rl_next_folio_no_for_property')) {
    function rl_next_folio_no_for_property(PDO $pdo, int $propertyId = 0): string
    {
        $prefix = 'FOL';
        if ($propertyId > 0 && rl_table_exists($pdo, 'properties') && rl_column_exists($pdo, 'properties', 'code')) {
            try {
                $stmt = $pdo->prepare("SELECT code FROM properties WHERE id = :id LIMIT 1");
                $stmt->execute([':id' => $propertyId]);
                $code = trim((string)($stmt->fetchColumn() ?: ''));
                if ($code !== '') $prefix = 'FOL-' . strtoupper($code);
            } catch (Throwable $e) {}
        }
        do {
            $folioNo = $prefix . '-' . date('YmdHis') . '-' . random_int(100, 999);
            $stmt = $pdo->prepare("SELECT COUNT(*) FROM folios WHERE folio_no = :folio_no");
            $stmt->execute([':folio_no' => $folioNo]);
        } while ((int)$stmt->fetchColumn() > 0);
        return $folioNo;
    }
}

if (!function_exists('rl_get_or_create_master_folio')) {
    function rl_get_or_create_master_folio(PDO $pdo, int $orderId): int
    {
        $stmt = $pdo->prepare("SELECT id FROM folios WHERE order_id = :order_id AND folio_type = 'master' LIMIT 1");
        $stmt->execute([':order_id' => $orderId]);
        $folioId = (int)($stmt->fetchColumn() ?: 0);
        if ($folioId > 0) return $folioId;

        $stmt = $pdo->prepare("SELECT o.property_id, o.order_no, g.firstname, g.lastname FROM orders o LEFT JOIN guests g ON g.id=o.guest_id WHERE o.id=:id LIMIT 1");
        $stmt->execute([':id' => $orderId]);
        $order = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$order) throw new RuntimeException('Booking not found while creating cancellation folio.');

        $propertyId = (int)($order['property_id'] ?? 0);
        $guestName = trim(($order['firstname'] ?? '') . ' ' . ($order['lastname'] ?? '')) ?: 'Guest';
        $folioNo = rl_next_folio_no_for_property($pdo, $propertyId);
        $folioName = 'Master Folio - ' . ($order['order_no'] ?: ('Booking #' . $orderId)) . ' - ' . $guestName;

        $pdo->prepare("INSERT INTO folios (property_id, order_id, folio_no, folio_name, folio_type, status, total_charges, total_payments, balance)
                       VALUES (:property_id,:order_id,:folio_no,:folio_name,'master',1,0,0,0)")
            ->execute([':property_id'=>$propertyId, ':order_id'=>$orderId, ':folio_no'=>$folioNo, ':folio_name'=>$folioName]);
        return (int)$pdo->lastInsertId();
    }
}

if (!function_exists('rl_live_order_balance')) {
    function rl_live_order_balance(PDO $pdo, int $orderId): float
    {
        $stmt = $pdo->prepare("SELECT COALESCE(SUM(COALESCE(debit,0)-COALESCE(credit,0)),0) FROM folio_transactions WHERE order_id=:order_id AND COALESCE(is_voided,0)=0");
        $stmt->execute([':order_id'=>$orderId]);
        return round((float)$stmt->fetchColumn(), 2);
    }
}

if (!function_exists('rl_insert_folio_transaction')) {
    function rl_insert_folio_transaction(PDO $pdo, array $order, int $folioId, string $txType, string $description, float $debit, float $credit): void
    {
        $orderId = (int)($order['id'] ?? 0);
        $propertyId = (int)($order['property_id'] ?? 0);
        $columns = ['folio_id','order_id','tx_date','tx_type','description','debit','credit','source_table','source_id'];
        $values  = [':folio_id',':order_id','NOW()',':tx_type',':description',':debit',':credit',':source_table',':source_id'];
        $params = [
            ':folio_id'=>$folioId, ':order_id'=>$orderId, ':tx_type'=>$txType,
            ':description'=>$description, ':debit'=>round(max(0,$debit),2), ':credit'=>round(max(0,$credit),2),
            ':source_table'=>'orders', ':source_id'=>$orderId,
        ];
        if (rl_column_exists($pdo, 'folio_transactions', 'property_id')) {
            array_unshift($columns, 'property_id'); array_unshift($values, ':property_id'); $params[':property_id'] = $propertyId;
        }
        if (rl_column_exists($pdo, 'folio_transactions', 'created_at')) { $columns[]='created_at'; $values[]='NOW()'; }
        if (rl_column_exists($pdo, 'folio_transactions', 'created_by')) { $columns[]='created_by'; $values[]=':created_by'; $params[':created_by']=rl_current_user_id(); }
        $pdo->prepare('INSERT INTO folio_transactions (`'.implode('`,`',$columns).'`) VALUES ('.implode(',',$values).')')->execute($params);
    }
}

if (!function_exists('rl_recalculate_folio_totals')) {
    function rl_recalculate_folio_totals(PDO $pdo, int $folioId): void
    {
        $stmt = $pdo->prepare("SELECT
                COALESCE(SUM(CASE WHEN COALESCE(is_voided,0)=0 AND LOWER(COALESCE(tx_type,'')) NOT IN ('payment','receipt','deposit','payment_received','folio_payment','guest_payment','payment_void','void_payment','payment_reversal','reversal','refund','payment_refund') AND LOWER(COALESCE(source_table,'')) NOT IN ('payment','payments','receipt','receipts') THEN COALESCE(debit,0) ELSE 0 END),0) AS total_charges,
                GREATEST(0, COALESCE(SUM(CASE WHEN COALESCE(is_voided,0)=0 AND (LOWER(COALESCE(tx_type,'')) IN ('payment','receipt','deposit','payment_received','folio_payment','guest_payment') OR LOWER(COALESCE(source_table,'')) IN ('payment','payments','receipt','receipts')) AND COALESCE(credit,0)>0 THEN COALESCE(credit,0) WHEN COALESCE(is_voided,0)=0 AND (LOWER(COALESCE(tx_type,'')) IN ('payment_void','void_payment','payment_reversal','reversal','refund','payment_refund') OR LOWER(COALESCE(description,'')) LIKE '%void%' OR LOWER(COALESCE(description,'')) LIKE '%reversal%' OR LOWER(COALESCE(description,'')) LIKE '%refund%') AND COALESCE(debit,0)>0 THEN -1*COALESCE(debit,0) ELSE 0 END),0)) AS total_payments
            FROM folio_transactions WHERE folio_id=:folio_id");
        $stmt->execute([':folio_id'=>$folioId]);
        $totals = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
        $charges = round((float)($totals['total_charges'] ?? 0), 2);
        $payments = round((float)($totals['total_payments'] ?? 0), 2);
        $balance = round($charges - $payments, 2);
        $pdo->prepare("UPDATE folios SET total_charges=:c,total_payments=:p,balance=:b,updated_at=NOW() WHERE id=:id")
            ->execute([':c'=>$charges, ':p'=>$payments, ':b'=>$balance, ':id'=>$folioId]);
    }
}

if (!function_exists('rl_recalculate_order_folios')) {
    function rl_recalculate_order_folios(PDO $pdo, int $orderId): void
    {
        $stmt = $pdo->prepare("SELECT id FROM folios WHERE order_id=:order_id");
        $stmt->execute([':order_id'=>$orderId]);
        foreach ($stmt->fetchAll(PDO::FETCH_COLUMN) as $folioId) rl_recalculate_folio_totals($pdo, (int)$folioId);
    }
}

if (!function_exists('rl_release_cancelled_inventory')) {
    function rl_release_cancelled_inventory(PDO $pdo, int $orderId): void
    {
        if (!rl_column_exists($pdo, 'order_rooming', 'released_status')) {
            try { $pdo->exec("ALTER TABLE `order_rooming` ADD COLUMN `released_status` TINYINT(1) NOT NULL DEFAULT 0"); } catch (Throwable $e) {}
        }
        if (!rl_column_exists($pdo, 'order_rooming', 'released_at')) {
            try { $pdo->exec("ALTER TABLE `order_rooming` ADD COLUMN `released_at` DATETIME NULL"); } catch (Throwable $e) {}
        }
        if (!rl_column_exists($pdo, 'order_rooming', 'release_reason')) {
            try { $pdo->exec("ALTER TABLE `order_rooming` ADD COLUMN `release_reason` VARCHAR(255) NULL"); } catch (Throwable $e) {}
        }

        $roomIds = [];
        try {
            $stmt = $pdo->prepare("SELECT DISTINCT room_id FROM order_rooming WHERE order_id=:order_id AND room_id IS NOT NULL AND room_id > 0");
            $stmt->execute([':order_id'=>$orderId]);
            foreach ($stmt->fetchAll(PDO::FETCH_COLUMN) as $rid) { $rid=(int)$rid; if ($rid>0) $roomIds[$rid]=$rid; }
        } catch (Throwable $e) {}

        $set = [];
        if (rl_column_exists($pdo, 'order_rooming', 'released_status')) $set[] = 'released_status = 1';
        if (rl_column_exists($pdo, 'order_rooming', 'released_at')) $set[] = 'released_at = NOW()';
        if (rl_column_exists($pdo, 'order_rooming', 'release_reason')) $set[] = "release_reason = 'Cancelled reservation'";
        if (rl_column_exists($pdo, 'order_rooming', 'checked_in_status')) $set[] = 'checked_in_status = 0';
        if (rl_column_exists($pdo, 'order_rooming', 'checked_out_status')) $set[] = 'checked_out_status = 1';
        if (rl_column_exists($pdo, 'order_rooming', 'check_out')) $set[] = 'check_out = check_in';
        if (rl_column_exists($pdo, 'order_rooming', 'updated_at')) $set[] = 'updated_at = NOW()';
        if ($set) $pdo->prepare('UPDATE order_rooming SET '.implode(', ', $set).' WHERE order_id=:order_id')->execute([':order_id'=>$orderId]);

        if ($roomIds && rl_table_exists($pdo, 'rooms') && rl_column_exists($pdo, 'rooms', 'status')) {
            foreach ($roomIds as $roomId) {
                $releasedClause = rl_column_exists($pdo, 'order_rooming', 'released_status') ? "AND COALESCE(rmg.released_status,0)=0" : "";
                $stmt = $pdo->prepare("SELECT COUNT(*) FROM order_rooming rmg INNER JOIN orders o ON o.id=rmg.order_id
                    WHERE rmg.room_id=:room_id AND rmg.order_id<>:order_id
                      AND DATE(o.check_in) <= CURDATE() AND DATE(o.check_out) > CURDATE()
                      AND COALESCE(rmg.checked_out_status,0)=0 {$releasedClause}
                      AND CAST(COALESCE(o.status,1) AS CHAR) NOT IN ('0')");
                $stmt->execute([':room_id'=>$roomId, ':order_id'=>$orderId]);
                if ((int)$stmt->fetchColumn() === 0) {
                    $pdo->prepare("UPDATE rooms SET status='available' WHERE id=:id")->execute([':id'=>$roomId]);
                }
            }
        }
    }
}

if (!function_exists('rl_nightsbridge_availability_push')) {
    /*
       Real-time Nightsbridge availability push after a cancel or reinstate on
       the reservation list. Without this, a cancel here only re-opened the room
       on the channel at the next cron sweep (under-sell), and a reinstate here
       re-consumed a room the channel still thought was free (OVERBOOK risk).
       Same proven pattern as move_booking.php / reservation_form.php:
         - called strictly AFTER the transaction has committed
         - only fires for the Nightsbridge-connected property
         - all failures swallowed; the nightly flush is the backstop.
    */
    function rl_nightsbridge_availability_push(PDO $pdo, int $orderId): void
    {
        try {
            if ($orderId <= 0) return;

            $propertyId = 0;
            try {
                $ps = $pdo->prepare("SELECT property_id FROM orders WHERE id = :id LIMIT 1");
                $ps->execute([':id' => $orderId]);
                $propertyId = (int)$ps->fetchColumn();
            } catch (Throwable $e) { return; }

            if (defined('NIGHTSBRIDGE_PROPERTY_ID') && $propertyId !== (int)NIGHTSBRIDGE_PROPERTY_ID) {
                return;
            }

            $typeIds = array();
            try {
                $rmgHasType = rl_column_exists($pdo, 'order_rooming', 'room_type_id');
                $rtidExpr = $rmgHasType
                    ? 'COALESCE(r.room_type_id, rmg.room_type_id)'
                    : 'r.room_type_id';
                $ts = $pdo->prepare("
                    SELECT DISTINCT {$rtidExpr} AS rtid
                    FROM order_rooming rmg
                    LEFT JOIN rooms r ON r.id = rmg.room_id
                    WHERE rmg.order_id = :id
                ");
                $ts->execute([':id' => $orderId]);
                foreach ($ts->fetchAll(PDO::FETCH_COLUMN) as $rtid) {
                    if ((int)$rtid > 0) $typeIds[] = (int)$rtid;
                }
            } catch (Throwable $e) { $typeIds = array(); }

            if (!function_exists('nb_run_inventory_push')) {
                $candidates = array(
                    __DIR__ . '/../../api/nightsbridge_inventory_push.php',
                    __DIR__ . '/../api/nightsbridge_inventory_push.php',
                    rtrim((string)(isset($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : ''), '/') . '/rooms/api/nightsbridge_inventory_push.php',
                );
                foreach ($candidates as $lib) {
                    if (is_file($lib)) {
                        if (!defined('NB_INVENTORY_PUSH_LIB')) define('NB_INVENTORY_PUSH_LIB', true);
                        require_once $lib;
                        break;
                    }
                }
            }
            if (!function_exists('nb_run_inventory_push')) return;

            $ids = array_values(array_unique(array_filter(array_map('intval', $typeIds))));
            nb_run_inventory_push($pdo, $ids ?: null, 'reservation_list', false);
        } catch (Throwable $e) {
            // Intentionally silent - the action has already committed.
        }
    }
}

if (!function_exists('rl_apply_cancellation_policy_to_folio')) {
    function rl_apply_cancellation_policy_to_folio(PDO $pdo, array $order, string $policyAction, float $requestedAdjustment, float $policyCharge, string $reason): array
    {
        $orderId = (int)($order['id'] ?? 0);
        $folioId = rl_get_or_create_master_folio($pdo, $orderId);
        $originalBalance = rl_live_order_balance($pdo, $orderId);
        $requestedAdjustment = round(max(0, $requestedAdjustment), 2);
        $policyCharge = round(max(0, $policyCharge), 2);
        $adjustmentAmount = 0.00;
        $postedCharge = 0.00;
        $bookingLabel = (string)($order['order_no'] ?: ('Booking #' . $orderId));
        $reasonSuffix = trim($reason) !== '' ? ' | ' . trim($reason) : '';

        if ($policyAction === 'keep_balance') {
            // Keep original balance exactly as it is.
        } elseif ($policyAction === 'waive_all') {
            $adjustmentAmount = max(0, $originalBalance);
        } elseif ($policyAction === 'waive_and_charge_policy') {
            $adjustmentAmount = min(max(0, $originalBalance), $requestedAdjustment);
            $postedCharge = $policyCharge;
        } else {
            throw new RuntimeException('Invalid cancellation accounting option selected.');
        }

        if ($adjustmentAmount > 0.00001) {
            rl_insert_folio_transaction($pdo, $order, $folioId, 'cancellation_adjustment', 'Cancellation Adjustment - ' . $bookingLabel . $reasonSuffix, 0, $adjustmentAmount);
        }
        if ($postedCharge > 0.00001) {
            rl_insert_folio_transaction($pdo, $order, $folioId, 'cancellation_policy_charge', 'Cancellation Policy Charge - ' . $bookingLabel . $reasonSuffix, $postedCharge, 0);
        }

        rl_recalculate_folio_totals($pdo, $folioId);
        rl_recalculate_order_folios($pdo, $orderId);
        return [
            'folio_id' => $folioId,
            'original_balance' => round($originalBalance,2),
            'adjustment_amount' => round($adjustmentAmount,2),
            'policy_charge' => round($postedCharge,2),
            'final_balance' => rl_live_order_balance($pdo, $orderId),
        ];
    }
}


$waitlistExists = false;
$waitlistCount = 0;
$waitlistAlerts = [];

try {
    $checkStmt = $pdo->query("SHOW TABLES LIKE 'reservation_waitlist'");
    $waitlistExists = (bool)$checkStmt->fetchColumn();
} catch (Throwable $e) {
    $waitlistExists = false;
}

if ($waitlistExists) {
    try {
        if (function_exists('refresh_waitlist_notifications')) {
            refresh_waitlist_notifications($pdo);
        }
    } catch (Throwable $e) {
        // Do not break the page if waitlist refresh fails
    }

    try {
        $waitlistCount = (int)$pdo->query("
            SELECT COUNT(*)
            FROM reservation_waitlist
            WHERE status = 'notified'
        ")->fetchColumn();
    } catch (Throwable $e) {
        $waitlistCount = 0;
    }

    try {
        $stmtWaitlistAlerts = $pdo->query("
            SELECT
                rw.id,
                rw.waitlist_no,
                rw.guest_name,
                rw.check_in,
                rw.check_out,
                rw.totalrooms,
                rw.adults,
                rw.kids,
                rw.available_rooms_last,
                rw.availability_found_at,
                p.name AS property_name,
                p.code AS property_code,
                rt.title AS room_type
            FROM reservation_waitlist rw
            LEFT JOIN properties p ON p.id = rw.property_id
            LEFT JOIN room_types rt ON rt.id = rw.room_type_id
            WHERE rw.status = 'notified'
            ORDER BY rw.availability_found_at DESC, rw.id DESC
            LIMIT 5
        ");
        $waitlistAlerts = $stmtWaitlistAlerts->fetchAll(PDO::FETCH_ASSOC);
    } catch (Throwable $e) {
        $waitlistAlerts = [];
    }
}


$cancellationErrors = [];
$cancellationSuccess = '';

try {
    if (rl_table_exists($pdo, 'orders')) {
        rl_try_add_column($pdo, 'orders', 'cancellation_at', '`cancellation_at` DATETIME NULL AFTER `status`');
        rl_try_add_column($pdo, 'orders', 'cancellation_reason', '`cancellation_reason` VARCHAR(255) NULL AFTER `cancellation_at`');
        rl_try_add_column($pdo, 'orders', 'cancellation_policy_action', '`cancellation_policy_action` VARCHAR(40) NULL AFTER `cancellation_reason`');
        rl_try_add_column($pdo, 'orders', 'cancellation_original_balance', '`cancellation_original_balance` DECIMAL(12,2) NOT NULL DEFAULT 0 AFTER `cancellation_policy_action`');
        rl_try_add_column($pdo, 'orders', 'cancellation_adjustment_amount', '`cancellation_adjustment_amount` DECIMAL(12,2) NOT NULL DEFAULT 0 AFTER `cancellation_original_balance`');
        rl_try_add_column($pdo, 'orders', 'cancellation_policy_charge', '`cancellation_policy_charge` DECIMAL(12,2) NOT NULL DEFAULT 0 AFTER `cancellation_adjustment_amount`');
        rl_try_add_column($pdo, 'orders', 'cancellation_final_balance', '`cancellation_final_balance` DECIMAL(12,2) NOT NULL DEFAULT 0 AFTER `cancellation_policy_charge`');
        rl_try_add_column($pdo, 'orders', 'cancellation_inventory_released', '`cancellation_inventory_released` TINYINT(1) NOT NULL DEFAULT 0 AFTER `cancellation_final_balance`');
        rl_try_add_column($pdo, 'orders', 'cancelled_by', '`cancelled_by` INT NULL AFTER `cancellation_inventory_released`');

        /* Front Desk no-show accounting fields. These are populated by frontdesk_dashboard.php / arrivals_today.php. */
        rl_try_add_column($pdo, 'orders', 'booking_status', '`booking_status` VARCHAR(40) NULL AFTER `status`');
        rl_try_add_column($pdo, 'orders', 'no_show_at', '`no_show_at` DATETIME NULL AFTER `booking_status`');
        rl_try_add_column($pdo, 'orders', 'no_show_reason', '`no_show_reason` VARCHAR(255) NULL AFTER `no_show_at`');
        rl_try_add_column($pdo, 'orders', 'no_show_policy_action', '`no_show_policy_action` VARCHAR(40) NULL AFTER `no_show_reason`');
        rl_try_add_column($pdo, 'orders', 'no_show_original_balance', '`no_show_original_balance` DECIMAL(12,2) NOT NULL DEFAULT 0 AFTER `no_show_policy_action`');
        rl_try_add_column($pdo, 'orders', 'no_show_adjustment_amount', '`no_show_adjustment_amount` DECIMAL(12,2) NOT NULL DEFAULT 0 AFTER `no_show_original_balance`');
        rl_try_add_column($pdo, 'orders', 'no_show_policy_charge', '`no_show_policy_charge` DECIMAL(12,2) NOT NULL DEFAULT 0 AFTER `no_show_adjustment_amount`');
        rl_try_add_column($pdo, 'orders', 'no_show_final_balance', '`no_show_final_balance` DECIMAL(12,2) NOT NULL DEFAULT 0 AFTER `no_show_policy_charge`');
        rl_try_add_column($pdo, 'orders', 'no_show_inventory_released', '`no_show_inventory_released` TINYINT(1) NOT NULL DEFAULT 0 AFTER `no_show_policy_charge`');
    }
} catch (Throwable $e) {}

if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && ($_POST['action'] ?? '') === 'cancel_reservation_policy') {
    try {
        if (function_exists('verify_csrf')) verify_csrf();
        $orderId = (int)($_POST['order_id'] ?? 0);
        $reason = trim((string)($_POST['cancel_reason'] ?? 'Reservation cancelled.'));
        $policyAction = trim((string)($_POST['cancel_policy_action'] ?? 'keep_balance'));
        $policyAdjustmentAmount = (float)($_POST['cancel_adjustment_amount'] ?? 0);
        $policyChargeAmount = (float)($_POST['cancel_policy_charge_amount'] ?? 0);
        $releaseInventory = !empty($_POST['release_inventory']) ? 1 : 0;
        $validActions = ['keep_balance', 'waive_all', 'waive_and_charge_policy'];

        if ($orderId <= 0) throw new RuntimeException('Invalid reservation selected.');
        if (!in_array($policyAction, $validActions, true)) throw new RuntimeException('Invalid cancellation policy option selected.');
        if ($reason === '') throw new RuntimeException('Please enter the cancellation reason.');

        $stmt = $pdo->prepare("SELECT * FROM orders WHERE id = :id LIMIT 1");
        $stmt->execute([':id' => $orderId]);
        $order = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$order) throw new RuntimeException('Reservation not found.');

        $stmt = $pdo->prepare("SELECT COUNT(*) FROM order_rooming WHERE order_id = :order_id AND COALESCE(checked_in_status,0)=1");
        $stmt->execute([':order_id' => $orderId]);
        if ((int)$stmt->fetchColumn() > 0) {
            throw new RuntimeException('This reservation has checked-in room slots. Check the guest out or reverse check-in before cancellation.');
        }

        $pdo->beginTransaction();

        $policyResult = rl_apply_cancellation_policy_to_folio($pdo, $order, $policyAction, $policyAdjustmentAmount, $policyChargeAmount, $reason);

        $set = ['status = 0'];
        $params = [':id' => $orderId];
        if (rl_column_exists($pdo, 'orders', 'cancellation_at')) $set[] = 'cancellation_at = NOW()';
        if (rl_column_exists($pdo, 'orders', 'cancellation_reason')) { $set[] = 'cancellation_reason = :cancel_reason'; $params[':cancel_reason'] = $reason; }
        if (rl_column_exists($pdo, 'orders', 'cancellation_policy_action')) { $set[] = 'cancellation_policy_action = :policy_action'; $params[':policy_action'] = $policyAction; }
        if (rl_column_exists($pdo, 'orders', 'cancellation_original_balance')) { $set[] = 'cancellation_original_balance = :original_balance'; $params[':original_balance'] = $policyResult['original_balance']; }
        if (rl_column_exists($pdo, 'orders', 'cancellation_adjustment_amount')) { $set[] = 'cancellation_adjustment_amount = :adjustment_amount'; $params[':adjustment_amount'] = $policyResult['adjustment_amount']; }
        if (rl_column_exists($pdo, 'orders', 'cancellation_policy_charge')) { $set[] = 'cancellation_policy_charge = :policy_charge'; $params[':policy_charge'] = $policyResult['policy_charge']; }
        if (rl_column_exists($pdo, 'orders', 'cancellation_final_balance')) { $set[] = 'cancellation_final_balance = :final_balance'; $params[':final_balance'] = $policyResult['final_balance']; }
        if (rl_column_exists($pdo, 'orders', 'cancelled_by')) { $set[] = 'cancelled_by = :cancelled_by'; $params[':cancelled_by'] = rl_current_user_id(); }
        if (rl_column_exists($pdo, 'orders', 'updated_on')) $set[] = 'updated_on = NOW()';
        if (rl_column_exists($pdo, 'orders', 'updated_at')) $set[] = 'updated_at = NOW()';
        $pdo->prepare('UPDATE orders SET ' . implode(', ', $set) . ' WHERE id = :id')->execute($params);

        if ($releaseInventory) {
            rl_release_cancelled_inventory($pdo, $orderId);
            if (rl_column_exists($pdo, 'orders', 'cancellation_inventory_released')) {
                $pdo->prepare('UPDATE orders SET cancellation_inventory_released = 1 WHERE id = :id')->execute([':id' => $orderId]);
            }
            if (function_exists('refresh_waitlist_notifications')) {
                try { refresh_waitlist_notifications($pdo); } catch (Throwable $e) {}
            }
        }

        $pdo->commit();
        rl_nightsbridge_availability_push($pdo, $orderId);
        header('Location: /rooms/modules/reservations/reservation_list.php?cancelled=1');
        exit;
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        $cancellationErrors[] = $e->getMessage();
    }
}

/* =========================================================================
   REINSTATE A CANCELLED BOOKING

   Cancellation releases inventory (order_rooming.released_status=1, room freed)
   and can zero the rooming check_out. Reinstating must therefore FIRST confirm
   the rooms are still free for the booking's dates, and only then restore the
   booking to an active status and re-hold the rooms.

   Availability rule per room: the room must not be occupied by ANY OTHER active
   (non-cancelled) booking whose stay overlaps this booking's dates. Rooms still
   in Holding (room_id = 0) are always reinstatable (nothing to clash with).
   ========================================================================= */
if (!function_exists('rl_check_reinstate_availability')) {
    function rl_check_reinstate_availability(PDO $pdo, int $orderId): array
    {
        // Booking dates come from the orders header (cancellation does not change these).
        $ord = $pdo->prepare("SELECT id, check_in, check_out FROM orders WHERE id = :id LIMIT 1");
        $ord->execute([':id' => $orderId]);
        $order = $ord->fetch(PDO::FETCH_ASSOC);
        if (!$order) {
            return ['ok' => false, 'available' => false, 'message' => 'Booking not found.', 'rooms' => []];
        }
        $checkIn = (string)$order['check_in'];
        $checkOut = (string)$order['check_out'];

        // Each allocated room in this booking.
        $stmt = $pdo->prepare("
            SELECT rmg.id AS rooming_id, rmg.room_id,
                   COALESCE(rm.room_no, '') AS room_no,
                   COALESCE(rt.title, '') AS room_type
            FROM order_rooming rmg
            LEFT JOIN rooms rm ON rm.id = rmg.room_id
            LEFT JOIN room_types rt ON rt.id = rmg.room_type_id
            WHERE rmg.order_id = :order_id
            ORDER BY rmg.id ASC");
        $stmt->execute([':order_id' => $orderId]);
        $roomingRows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];

        $rooms = [];
        $allAvailable = true;

        foreach ($roomingRows as $rr) {
            $roomId = (int)$rr['room_id'];
            $roomNo = (string)$rr['room_no'];
            $roomType = (string)$rr['room_type'];

            if ($roomId <= 0) {
                // In Holding - nothing to clash with, always reinstatable.
                $rooms[] = ['room_no' => $roomNo !== '' ? $roomNo : 'Holding', 'room_type' => $roomType, 'available' => true, 'clash' => null];
                continue;
            }

            // Any OTHER active booking overlapping [check_in, check_out) on this room?
            $clashStmt = $pdo->prepare("
                SELECT o.order_no, g.firstname, g.lastname, o.check_in, o.check_out
                FROM order_rooming rmg
                INNER JOIN orders o ON o.id = rmg.order_id
                LEFT JOIN guests g ON g.id = o.guest_id
                WHERE rmg.room_id = :room_id
                  AND rmg.order_id <> :order_id
                  AND COALESCE(rmg.released_status, 0) = 0
                  AND CAST(COALESCE(o.status, 1) AS CHAR) NOT IN ('0')
                  AND LOWER(COALESCE(o.booking_status,'')) NOT IN ('no_show','noshow')
                  AND o.no_show_at IS NULL
                  AND DATE(o.check_in) < DATE(:check_out)
                  AND DATE(o.check_out) > DATE(:check_in)
                LIMIT 1");
            $clashStmt->execute([
                ':room_id' => $roomId,
                ':order_id' => $orderId,
                ':check_in' => $checkIn,
                ':check_out' => $checkOut,
            ]);
            $clash = $clashStmt->fetch(PDO::FETCH_ASSOC);

            $isAvailable = !$clash;
            if (!$isAvailable) $allAvailable = false;

            $rooms[] = [
                'room_no' => $roomNo !== '' ? $roomNo : ('#' . $roomId),
                'room_type' => $roomType,
                'available' => $isAvailable,
                'clash' => $clash ? [
                    'order_no' => (string)($clash['order_no'] ?: ''),
                    'guest' => trim((string)($clash['firstname'] ?? '') . ' ' . (string)($clash['lastname'] ?? '')),
                    'check_in' => substr((string)$clash['check_in'], 0, 10),
                    'check_out' => substr((string)$clash['check_out'], 0, 10),
                ] : null,
            ];
        }

        return [
            'ok' => true,
            'available' => $allAvailable,
            'check_in' => substr($checkIn, 0, 10),
            'check_out' => substr($checkOut, 0, 10),
            'rooms' => $rooms,
        ];
    }
}

if (isset($_GET['ajax']) && $_GET['ajax'] === 'reinstate_check') {
    header('Content-Type: application/json; charset=utf-8');
    $orderId = (int)($_GET['order_id'] ?? 0);
    if ($orderId <= 0) { echo json_encode(['ok' => false, 'message' => 'Missing booking.']); exit; }
    try {
        echo json_encode(rl_check_reinstate_availability($pdo, $orderId));
    } catch (Throwable $e) {
        echo json_encode(['ok' => false, 'available' => false, 'message' => 'Availability check failed: ' . $e->getMessage(), 'rooms' => []]);
    }
    exit;
}

if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && ($_POST['action'] ?? '') === 'reinstate_reservation') {
    try {
        if (function_exists('verify_csrf')) verify_csrf();
        $orderId = (int)($_POST['order_id'] ?? 0);
        if ($orderId <= 0) throw new RuntimeException('Invalid reservation selected.');

        $stmt = $pdo->prepare("SELECT * FROM orders WHERE id = :id LIMIT 1");
        $stmt->execute([':id' => $orderId]);
        $order = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$order) throw new RuntimeException('Reservation not found.');
        if ((string)($order['status'] ?? '') !== '0') throw new RuntimeException('This booking is not cancelled.');

        // Re-check availability at commit time (state may have changed since preview).
        $availability = rl_check_reinstate_availability($pdo, $orderId);
        if (empty($availability['available'])) {
            $busy = [];
            foreach (($availability['rooms'] ?? []) as $rm) {
                if (empty($rm['available'])) $busy[] = $rm['room_no'];
            }
            throw new RuntimeException('Cannot reinstate: room(s) ' . implode(', ', $busy) . ' are no longer available for these dates.');
        }

        $pdo->beginTransaction();

        // Restore booking to Confirmed and clear cancellation markers.
        $set = ['status = 1'];
        $params = [':id' => $orderId];
        if (rl_column_exists($pdo, 'orders', 'cancellation_at')) $set[] = 'cancellation_at = NULL';
        if (rl_column_exists($pdo, 'orders', 'cancellation_inventory_released')) $set[] = 'cancellation_inventory_released = 0';
        if (rl_column_exists($pdo, 'orders', 'booking_status')) $set[] = "booking_status = NULL";
        if (rl_column_exists($pdo, 'orders', 'no_show_at')) $set[] = 'no_show_at = NULL';
        if (rl_column_exists($pdo, 'orders', 'updated_on')) $set[] = 'updated_on = NOW()';
        if (rl_column_exists($pdo, 'orders', 'updated_at')) $set[] = 'updated_at = NOW()';
        $pdo->prepare('UPDATE orders SET ' . implode(', ', $set) . ' WHERE id = :id')->execute($params);

        // Un-release rooming rows and restore the stay dates from the order header.
        $roomingSet = [];
        if (rl_column_exists($pdo, 'order_rooming', 'released_status')) $roomingSet[] = 'released_status = 0';
        if (rl_column_exists($pdo, 'order_rooming', 'released_at')) $roomingSet[] = 'released_at = NULL';
        if (rl_column_exists($pdo, 'order_rooming', 'release_reason')) $roomingSet[] = 'release_reason = NULL';
        if (rl_column_exists($pdo, 'order_rooming', 'checked_out_status')) $roomingSet[] = 'checked_out_status = 0';
        if (rl_column_exists($pdo, 'order_rooming', 'check_out')) $roomingSet[] = 'check_out = :check_out';
        if (rl_column_exists($pdo, 'order_rooming', 'updated_at')) $roomingSet[] = 'updated_at = NOW()';
        $roomingParams = [':order_id' => $orderId];
        if (rl_column_exists($pdo, 'order_rooming', 'check_out')) $roomingParams[':check_out'] = (string)$order['check_out'];
        if ($roomingSet) {
            $pdo->prepare('UPDATE order_rooming SET ' . implode(', ', $roomingSet) . ' WHERE order_id = :order_id')->execute($roomingParams);
        }

        // Re-hold the physical rooms (mark occupied/booked) if the rooms table tracks status.
        if (rl_table_exists($pdo, 'rooms') && rl_column_exists($pdo, 'rooms', 'status')) {
            $rid = $pdo->prepare("SELECT DISTINCT room_id FROM order_rooming WHERE order_id = :order_id AND room_id > 0");
            $rid->execute([':order_id' => $orderId]);
            foreach ($rid->fetchAll(PDO::FETCH_COLUMN) as $roomId) {
                $pdo->prepare("UPDATE rooms SET status = 'booked' WHERE id = :id")->execute([':id' => (int)$roomId]);
            }
        }

        $pdo->commit();
        rl_nightsbridge_availability_push($pdo, $orderId);
        header('Location: /rooms/modules/reservations/reservation_list.php?reinstated=1');
        exit;
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        $cancellationErrors[] = $e->getMessage();
    }
}

/* =========================================================================
   EDIT PRICING (per-date rate override grid)

   Opens an Excel-like editor for a single booking's nightly accommodation
   prices held in rel_orders_prices (one row per order per date). Staff can
   override the rate for any night and save. We reuse the existing override
   columns (rate_overridden / original_total / override_total / override_type
   / override_value) when they exist, and degrade gracefully to a plain total
   update when they do not, so this works on every installation.

   rel_orders_prices is keyed by order + date, so pricing is edited per night
   for the whole booking (not per room/guest, which the schema does not store
   separately). Adults/children are shown per night for context, read from the
   order header.
   ========================================================================= */
if (isset($_GET['ajax']) && $_GET['ajax'] === 'pricing_load') {
    header('Content-Type: application/json; charset=utf-8');
    $respond = function (array $payload) { echo json_encode($payload); exit; };

    $orderId = (int)($_GET['order_id'] ?? 0);
    if ($orderId <= 0) $respond(['ok' => false, 'message' => 'Missing booking.']);

    try {
        // Header context (guest, room mix, adults/children, dates).
        $ord = $pdo->prepare("
            SELECT o.id, o.order_no, o.check_in, o.check_out, o.nights,
                   o.adults, o.kids, o.totalrooms,
                   TRIM(CONCAT(COALESCE(g.firstname,''),' ',COALESCE(g.lastname,''))) AS guest_name,
                   o.group_name
            FROM orders o
            LEFT JOIN guests g ON g.id = o.guest_id
            WHERE o.id = :id LIMIT 1");
        $ord->execute([':id' => $orderId]);
        $order = $ord->fetch(PDO::FETCH_ASSOC);
        if (!$order) $respond(['ok' => false, 'message' => 'Booking not found.']);

        // Which optional columns exist on rel_orders_prices?
        $hasOverridden = rl_column_exists($pdo, 'rel_orders_prices', 'rate_overridden');
        $hasOriginal   = rl_column_exists($pdo, 'rel_orders_prices', 'original_total');
        $hasOverrideT  = rl_column_exists($pdo, 'rel_orders_prices', 'override_total');
        $hasAdults     = rl_column_exists($pdo, 'rel_orders_prices', 'adults');
        $hasChildren   = rl_column_exists($pdo, 'rel_orders_prices', 'children')
                         ?: rl_column_exists($pdo, 'rel_orders_prices', 'kids');
        $childCol      = rl_column_exists($pdo, 'rel_orders_prices', 'children') ? 'children'
                         : (rl_column_exists($pdo, 'rel_orders_prices', 'kids') ? 'kids' : null);

        $cols = ["id", "`date`", "COALESCE(total,0) AS total"];
        if ($hasOverridden) $cols[] = "COALESCE(rate_overridden,0) AS rate_overridden";
        if ($hasOriginal)   $cols[] = "original_total";
        if ($hasOverrideT)  $cols[] = "override_total";
        if ($hasAdults)     $cols[] = "COALESCE(adults,0) AS row_adults";
        if ($childCol)      $cols[] = "COALESCE(`{$childCol}`,0) AS row_children";

        $ps = $pdo->prepare("SELECT " . implode(', ', $cols) . "
                             FROM rel_orders_prices
                             WHERE order_id = :order_id
                             ORDER BY `date` ASC, id ASC");
        $ps->execute([':order_id' => $orderId]);
        $priceRows = $ps->fetchAll(PDO::FETCH_ASSOC) ?: [];

        $defaultAdults = (int)($order['adults'] ?? 0);
        $defaultKids   = (int)($order['kids'] ?? 0);

        $rows = [];
        $sysTotal = 0.0;
        foreach ($priceRows as $r) {
            $total = round((float)$r['total'], 2);
            // The "system rate" is the originally-calculated figure when an
            // override is in place, else the current total.
            $system = $total;
            if ($hasOverridden && (int)($r['rate_overridden'] ?? 0) === 1 && $hasOriginal && $r['original_total'] !== null) {
                $system = round((float)$r['original_total'], 2);
            }
            $sysTotal += $total;
            $rows[] = [
                'id'        => (int)$r['id'],
                'date'      => substr((string)$r['date'], 0, 10),
                'adults'    => isset($r['row_adults']) ? (int)$r['row_adults'] : $defaultAdults,
                'children'  => isset($r['row_children']) ? (int)$r['row_children'] : $defaultKids,
                'system'    => $system,
                'total'     => $total,
                'overridden'=> $hasOverridden ? ((int)($r['rate_overridden'] ?? 0) === 1) : ($system !== $total),
            ];
        }

        $respond([
            'ok' => true,
            'order' => [
                'id'         => (int)$order['id'],
                'order_no'   => $order['order_no'] ?: ('#' . $order['id']),
                'guest'      => trim((string)$order['group_name']) !== '' ? trim((string)$order['group_name']) : (trim((string)$order['guest_name']) !== '' ? trim((string)$order['guest_name']) : ('Booking #' . $order['id'])),
                'check_in'   => substr((string)$order['check_in'], 0, 10),
                'check_out'  => substr((string)$order['check_out'], 0, 10),
                'rooms'      => (int)$order['totalrooms'],
            ],
            'rows' => $rows,
            'total' => round($sysTotal, 2),
            'supports_override' => $hasOverridden,
        ]);
    } catch (Throwable $e) {
        $respond(['ok' => false, 'message' => 'Could not load pricing: ' . $e->getMessage()]);
    }
}

if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && ($_POST['action'] ?? '') === 'pricing_save') {
    header('Content-Type: application/json; charset=utf-8');
    $respond = function (array $payload) { echo json_encode($payload); exit; };

    try {
        if (function_exists('verify_csrf')) verify_csrf();

        $orderId = (int)($_POST['order_id'] ?? 0);
        $rowsJson = (string)($_POST['rows'] ?? '[]');
        if ($orderId <= 0) throw new RuntimeException('Missing booking.');

        $rows = json_decode($rowsJson, true);
        if (!is_array($rows)) throw new RuntimeException('Malformed pricing data.');

        $hasOverridden = rl_column_exists($pdo, 'rel_orders_prices', 'rate_overridden');
        $hasOriginal   = rl_column_exists($pdo, 'rel_orders_prices', 'original_total');
        $hasOverrideT  = rl_column_exists($pdo, 'rel_orders_prices', 'override_total');
        $hasOverrideTy = rl_column_exists($pdo, 'rel_orders_prices', 'override_type');
        $hasOverrideVal= rl_column_exists($pdo, 'rel_orders_prices', 'override_value');

        $pdo->beginTransaction();

        // Verify each price row belongs to this order before touching it.
        $check = $pdo->prepare("SELECT id, COALESCE(total,0) AS total" .
            ($hasOriginal ? ", original_total" : "") .
            ($hasOverridden ? ", COALESCE(rate_overridden,0) AS rate_overridden" : "") .
            " FROM rel_orders_prices WHERE id = :id AND order_id = :order_id LIMIT 1");

        $updatedCount = 0;
        foreach ($rows as $row) {
            $priceId = (int)($row['id'] ?? 0);
            if ($priceId <= 0) continue;
            if (!array_key_exists('override', $row)) continue;

            $overrideRaw = trim((string)$row['override']);
            $check->execute([':id' => $priceId, ':order_id' => $orderId]);
            $existing = $check->fetch(PDO::FETCH_ASSOC);
            if (!$existing) continue;

            $currentTotal = round((float)$existing['total'], 2);
            // Baseline "system" price to preserve as original_total the first
            // time we override a night.
            $baseline = $currentTotal;
            if ($hasOriginal && $existing['original_total'] !== null && (float)$existing['original_total'] > 0) {
                $baseline = round((float)$existing['original_total'], 2);
            }

            if ($overrideRaw === '') {
                // Blank override => revert this night to its system/original price.
                if ($hasOverridden) {
                    $sets = ["total = :total", "rate_overridden = 0"];
                    $params = [':total' => $baseline, ':id' => $priceId];
                    if ($hasOverrideT)  { $sets[] = "override_total = NULL"; }
                    if ($hasOverrideVal){ $sets[] = "override_value = NULL"; }
                    if ($hasOverrideTy) { $sets[] = "override_type = NULL"; }
                    $pdo->prepare("UPDATE rel_orders_prices SET " . implode(', ', $sets) . " WHERE id = :id")->execute($params);
                    $updatedCount++;
                }
                continue;
            }

            $overrideVal = round((float)str_replace(',', '', $overrideRaw), 2);
            if ($overrideVal < 0) throw new RuntimeException('Rate cannot be negative.');
            // No change from what's already stored? skip.
            if (abs($overrideVal - $currentTotal) < 0.001 && (!$hasOverridden || (int)($existing['rate_overridden'] ?? 0) === 1)) {
                continue;
            }

            if ($hasOverridden) {
                $sets = ["total = :total", "rate_overridden = 1"];
                $params = [':total' => $overrideVal, ':id' => $priceId];
                if ($hasOriginal)   { $sets[] = "original_total = COALESCE(NULLIF(original_total,0), :orig)"; $params[':orig'] = $baseline; }
                if ($hasOverrideT)  { $sets[] = "override_total = :ovr_total"; $params[':ovr_total'] = $overrideVal; }
                if ($hasOverrideVal){ $sets[] = "override_value = :ovr_value"; $params[':ovr_value'] = $overrideVal; }
                if ($hasOverrideTy) { $sets[] = "override_type = :ovr_type"; $params[':ovr_type'] = 'manual'; }
                $pdo->prepare("UPDATE rel_orders_prices SET " . implode(', ', $sets) . " WHERE id = :id")->execute($params);
            } else {
                // Minimal installs: just set the total.
                $pdo->prepare("UPDATE rel_orders_prices SET total = :total WHERE id = :id")
                    ->execute([':total' => $overrideVal, ':id' => $priceId]);
            }
            $updatedCount++;
        }

        // Keep the order header total and the master folio room charges in step.
        $sumStmt = $pdo->prepare("SELECT COALESCE(SUM(total),0) FROM rel_orders_prices WHERE order_id = :id");
        $sumStmt->execute([':id' => $orderId]);
        $newRoomTotal = round((float)$sumStmt->fetchColumn(), 2);

        if (rl_column_exists($pdo, 'orders', 'totalamount')) {
            $pdo->prepare("UPDATE orders SET totalamount = :t" .
                (rl_column_exists($pdo, 'orders', 'updated_on') ? ", updated_on = NOW()" : "") .
                " WHERE id = :id")->execute([':t' => $newRoomTotal, ':id' => $orderId]);
        }

        // Recalculate folios so the balance reflects the new room charges.
        if (function_exists('rl_recalculate_order_folios')) {
            try { rl_recalculate_order_folios($pdo, $orderId); } catch (Throwable $e) {}
        }

        $pdo->commit();

        $respond(['ok' => true, 'message' => 'Manual rates saved.', 'updated' => $updatedCount, 'room_total' => $newRoomTotal]);
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        $respond(['ok' => false, 'message' => 'Could not save pricing: ' . $e->getMessage()]);
    }
}

$properties = $pdo->query("
    SELECT id, name, code
    FROM properties
    WHERE is_active = 1
    ORDER BY name ASC
")->fetchAll(PDO::FETCH_ASSOC);

$filterProperty = trim((string)($_GET['property_id'] ?? ''));
$filterFrom     = trim((string)($_GET['from'] ?? ''));
$filterTo       = trim((string)($_GET['to'] ?? ''));
$filterStatus   = trim((string)($_GET['status'] ?? ''));
$search         = trim((string)($_GET['q'] ?? ''));

/*
   Fast two-stage reservation search.

   The old query joined and grouped the complete orders, folios, transactions,
   rooming and pricing tables before applying the user's search text. On a large
   PMS database this becomes very slow.

   New approach:
   1. Find a small candidate set directly from orders using lightweight EXISTS
      checks against guest, agent, rooming and user tables.
   2. Limit the candidate set to the newest 300 matching bookings.
   3. Run the expensive totals/folio/rooming joins only for those candidates.

   Initial unfiltered page remains limited to the latest 30 bookings.
*/
$hasFilters = (
    $filterProperty !== ''
    || $filterFrom !== ''
    || $filterTo !== ''
    || $filterStatus !== ''
    || $search !== ''
);

$candidateWhere = ['1=1'];
$candidateParams = [];

if ($filterProperty !== '' && (int)$filterProperty > 0) {
    $candidateWhere[] = 'os.property_id = :candidate_property_id';
    $candidateParams[':candidate_property_id'] = (int)$filterProperty;
}

if ($filterFrom !== '') {
    $candidateWhere[] = 'os.check_in >= :candidate_from_date';
    $candidateParams[':candidate_from_date'] = $filterFrom;
}

if ($filterTo !== '') {
    $candidateWhere[] = 'os.check_out <= :candidate_to_date';
    $candidateParams[':candidate_to_date'] = $filterTo;
}

/*
   Header-level statuses can be applied before the expensive joins.
   In-house and checked-out are derived from rooming status, so those remain
   in the outer query where rstat is available.
*/
if ($filterStatus !== '') {
    if ($filterStatus === 'no_show') {
        $candidateWhere[] = "(LOWER(COALESCE(os.booking_status,'')) IN ('no_show','noshow') OR os.no_show_at IS NOT NULL)";
    } elseif (in_array($filterStatus, ['0', '1', '2'], true)) {
        $candidateWhere[] = 'os.status = :candidate_status';
        $candidateParams[':candidate_status'] = $filterStatus;

        if ($filterStatus !== '0') {
            $candidateWhere[] = "LOWER(COALESCE(os.booking_status,'')) NOT IN ('no_show','noshow')";
            $candidateWhere[] = 'os.no_show_at IS NULL';
        }
    }
}

if ($search !== '') {
    $candidateLike = '%' . $search . '%';
    $candidateExactId = ctype_digit($search) ? (int)$search : 0;

    $searchConditions = [];

    if ($candidateExactId > 0) {
        $searchConditions[] = 'os.id = :candidate_exact_id';
        $candidateParams[':candidate_exact_id'] = $candidateExactId;
    }

    $searchConditions[] = 'os.order_no LIKE :search_order_no';
    $candidateParams[':search_order_no'] = $candidateLike;

    $searchConditions[] = 'os.voucher_no LIKE :search_voucher_no';
    $candidateParams[':search_voucher_no'] = $candidateLike;

    $searchConditions[] = 'os.originating_agent LIKE :search_originating_agent';
    $candidateParams[':search_originating_agent'] = $candidateLike;

    $searchConditions[] = "EXISTS (
        SELECT 1
        FROM guests sg
        WHERE sg.id = os.guest_id
          AND (
                sg.firstname LIKE :search_guest_firstname
                OR sg.lastname LIKE :search_guest_lastname
                OR CONCAT(COALESCE(sg.firstname,''), ' ', COALESCE(sg.lastname,'')) LIKE :search_guest_fullname
          )
    )";
    $candidateParams[':search_guest_firstname'] = $candidateLike;
    $candidateParams[':search_guest_lastname'] = $candidateLike;
    $candidateParams[':search_guest_fullname'] = $candidateLike;

    $searchConditions[] = "EXISTS (
        SELECT 1
        FROM properties sp
        WHERE sp.id = os.property_id
          AND (
                sp.name LIKE :search_property_name
                OR sp.code LIKE :search_property_code
          )
    )";
    $candidateParams[':search_property_name'] = $candidateLike;
    $candidateParams[':search_property_code'] = $candidateLike;

    $searchConditions[] = "EXISTS (
        SELECT 1
        FROM agents sa
        WHERE sa.id = os.agent_id
          AND CONCAT(COALESCE(sa.firstname,''), ' ', COALESCE(sa.lastname,'')) LIKE :search_agent_name
    )";
    $candidateParams[':search_agent_name'] = $candidateLike;

    $searchConditions[] = "EXISTS (
        SELECT 1
        FROM consultants sc
        WHERE sc.id = os.contact_id
          AND CONCAT(COALESCE(sc.firstname,''), ' ', COALESCE(sc.lastname,'')) LIKE :search_consultant_name
    )";
    $candidateParams[':search_consultant_name'] = $candidateLike;

    $searchConditions[] = "EXISTS (
        SELECT 1
        FROM nationality sn
        WHERE sn.id = os.nationality_id
          AND sn.nationality LIKE :search_nationality
    )";
    $candidateParams[':search_nationality'] = $candidateLike;

    /*
     * Only reference optional rooming columns when they actually exist.
     * This avoids SQL errors on installations with an older order_rooming schema.
     */
    $roomSearchParts = [
        'srt.title LIKE :search_room_type',
        'CAST(sr.room_no AS CHAR) LIKE :search_room_slot',
        'srm.room_no LIKE :search_actual_room'
    ];
    $candidateParams[':search_room_type'] = $candidateLike;
    $candidateParams[':search_room_slot'] = $candidateLike;
    $candidateParams[':search_actual_room'] = $candidateLike;

    if (rl_column_exists($pdo, 'order_rooming', 'room_setup_notes')) {
        $roomSearchParts[] = "COALESCE(sr.room_setup_notes,'') LIKE :search_room_setup_notes";
        $candidateParams[':search_room_setup_notes'] = $candidateLike;
    }

    if (rl_column_exists($pdo, 'order_rooming', 'housekeeping_notes')) {
        $roomSearchParts[] = "COALESCE(sr.housekeeping_notes,'') LIKE :search_housekeeping_notes";
        $candidateParams[':search_housekeeping_notes'] = $candidateLike;
    }

    if (rl_column_exists($pdo, 'order_rooming', 'special_occasion')) {
        $roomSearchParts[] = "COALESCE(sr.special_occasion,'') LIKE :search_special_occasion";
        $candidateParams[':search_special_occasion'] = $candidateLike;
    }

    $searchConditions[] = "EXISTS (
        SELECT 1
        FROM order_rooming sr
        LEFT JOIN room_types srt ON srt.id = sr.room_type_id
        LEFT JOIN rooms srm ON srm.id = sr.room_id
        WHERE sr.order_id = os.id
          AND (" . implode("\n               OR ", $roomSearchParts) . ")
    )";

    $candidateWhere[] = '(' . implode("\n        OR ", $searchConditions) . ')';
}

if ($hasFilters) {
    $ordersSourceSql = "(
        SELECT os.*
        FROM orders os
        WHERE " . implode("\n          AND ", $candidateWhere) . "
        ORDER BY os.id DESC
        LIMIT 300
    ) o";
} else {
    $ordersSourceSql = "(SELECT * FROM orders ORDER BY id DESC LIMIT 30) o";
}

$hasRateOverrideColumn = rl_column_exists($pdo, 'rel_orders_prices', 'rate_overridden');
$hasOriginalTotalColumn = rl_column_exists($pdo, 'rel_orders_prices', 'original_total');
$hasOverrideTotalColumn = rl_column_exists($pdo, 'rel_orders_prices', 'override_total');
$hasOverrideTypeColumn = rl_column_exists($pdo, 'rel_orders_prices', 'override_type');
$hasOverrideValueColumn = rl_column_exists($pdo, 'rel_orders_prices', 'override_value');
$hasExtraBedOverrideColumn = rl_column_exists($pdo, 'rel_orders_prices', 'extra_bed_override');

$hasOrdersCreatedByColumn = rl_column_exists($pdo, 'orders', 'created_by');
$hasOrdersUpdatedByColumn = rl_column_exists($pdo, 'orders', 'updated_by');
$hasUsersTable = rl_table_exists($pdo, 'users');
$hasConsultantsTable = rl_table_exists($pdo, 'consultants');
$hasExtraChargeItemsTable = rl_table_exists($pdo, 'extra_charge_items');

$expectedArrivalSelectSql = rl_column_exists($pdo, 'orders', 'expected_arrival_time')
    ? "o.expected_arrival_time,"
    : "'14:00:00' AS expected_arrival_time,";

$roomExtraBedsExpr = rl_column_exists($pdo, 'order_rooming', 'extra_beds_required')
    ? "COALESCE(SUM(COALESCE(extra_beds_required,0)),0)"
    : "0";
$roomCotsExpr = rl_column_exists($pdo, 'order_rooming', 'cots_required')
    ? "COALESCE(SUM(COALESCE(cots_required,0)),0)"
    : "0";
$roomWheelchairExpr = rl_column_exists($pdo, 'order_rooming', 'wheelchair_room_required')
    ? "MAX(COALESCE(wheelchair_room_required,0))"
    : "0";
$roomInterleadingExpr = rl_column_exists($pdo, 'order_rooming', 'interleading_required')
    ? "MAX(COALESCE(interleading_required,0))"
    : "0";
$roomSetupNotesExpr = rl_column_exists($pdo, 'order_rooming', 'room_setup_notes')
    ? "GROUP_CONCAT(DISTINCT NULLIF(room_setup_notes,'') ORDER BY room_no SEPARATOR '; ')"
    : "''";
$roomHousekeepingNotesExpr = rl_column_exists($pdo, 'order_rooming', 'housekeeping_notes')
    ? "GROUP_CONCAT(DISTINCT NULLIF(housekeeping_notes,'') ORDER BY room_no SEPARATOR '; ')"
    : "''";
$roomSpecialOccasionExpr = rl_column_exists($pdo, 'order_rooming', 'special_occasion')
    ? "GROUP_CONCAT(DISTINCT NULLIF(special_occasion,'') ORDER BY room_no SEPARATOR '; ')"
    : "''";

/*
   Extras now have a proper detail table created by extras.php:
   extra_charge_items links each folio transaction to the actual source item
   (restaurant_menu_items / inventory_items / tblservices), quantity, unit price,
   and line total.

   Use it first for Extras totals because it is more accurate than classifying
   folio_transactions by tx_type/source_table. Keep the old folio_transactions
   calculation as a fallback for older historical postings.
*/
$extrasTotalExpr = $hasExtraChargeItemsTable
    ? "COALESCE(exi.linked_extras_total, ex.extras_total, 0)"
    : "COALESCE(ex.extras_total, 0)";

$extraItemsJoinSql = $hasExtraChargeItemsTable ? "
    LEFT JOIN (
        SELECT
            ft.order_id,
            COALESCE(SUM(CASE WHEN COALESCE(ft.is_voided, 0) = 0 THEN COALESCE(eci.line_total, 0) ELSE 0 END), 0) AS linked_extras_total,
            COUNT(*) AS linked_extra_item_count
        FROM extra_charge_items eci
        INNER JOIN folio_transactions ft ON ft.id = eci.folio_transaction_id
        GROUP BY ft.order_id
    ) exi ON exi.order_id = o.id
" : "";


$createdBySelectSql = $hasOrdersCreatedByColumn
    ? "o.created_by,"
    : "NULL AS created_by,";
$updatedBySelectSql = $hasOrdersUpdatedByColumn
    ? "o.updated_by,"
    : "NULL AS updated_by,";
$createdByNameSelectSql = ($hasOrdersCreatedByColumn && $hasUsersTable)
    ? "TRIM(CONCAT(COALESCE(cb.firstname,''), ' ', COALESCE(cb.lastname,''))) AS created_by_name,"
    : "'' AS created_by_name,";
$updatedByNameSelectSql = ($hasOrdersUpdatedByColumn && $hasUsersTable)
    ? "TRIM(CONCAT(COALESCE(ub.firstname,''), ' ', COALESCE(ub.lastname,''))) AS updated_by_name,"
    : "'' AS updated_by_name,";
$createdByJoinSql = ($hasOrdersCreatedByColumn && $hasUsersTable)
    ? "LEFT JOIN users cb ON cb.id = o.created_by"
    : "";
$updatedByJoinSql = ($hasOrdersUpdatedByColumn && $hasUsersTable)
    ? "LEFT JOIN users ub ON ub.id = o.updated_by"
    : "";

$overrideSelectSql = $hasRateOverrideColumn ? "
    LEFT JOIN (
        SELECT
            order_id,
            SUM(CASE WHEN COALESCE(rate_overridden,0) = 1 THEN 1 ELSE 0 END) AS override_nights,
            " . ($hasOriginalTotalColumn ? "SUM(CASE WHEN COALESCE(rate_overridden,0) = 1 THEN COALESCE(original_total,total,0) ELSE COALESCE(total,0) END)" : "SUM(COALESCE(total,0))") . " AS calculated_room_total,
            SUM(COALESCE(total,0)) AS final_room_total,
            " . ($hasOverrideTypeColumn ? "GROUP_CONCAT(DISTINCT NULLIF(override_type,'') ORDER BY override_type SEPARATOR ', ')" : "''") . " AS override_types
        FROM rel_orders_prices
        GROUP BY order_id
    ) rateov ON rateov.order_id = o.id
" : "
    LEFT JOIN (
        SELECT
            order_id,
            0 AS override_nights,
            SUM(COALESCE(total,0)) AS calculated_room_total,
            SUM(COALESCE(total,0)) AS final_room_total,
            '' AS override_types
        FROM rel_orders_prices
        GROUP BY order_id
    ) rateov ON rateov.order_id = o.id
";

$sql = "
    SELECT
        o.id,
        o.property_id,
        o.order_no,
        o.check_in,
        o.check_out,
        o.nights,
        o.adults,
        o.kids,
        o.totalrooms,
        o.totalamount,
        COALESCE(rateov.override_nights, 0) AS override_nights,

        /*
           IMPORTANT FIX:
           Match the Payments page accounting source.

           payments.php recalculates folios.total_charges correctly by excluding
           payment void/reversal rows from booking value. Therefore the
           reservation list must use the same folio summary when it exists.

           Correct result:
           Accommodation 600, Payment 600, Void Payment 600 =>
             Booking Value 600, Paid 0, Voided 600, Balance 600.
        */
        CASE
            WHEN COALESCE(ftcalc.live_transaction_count, fs.folio_count, 0) > 0
            THEN GREATEST(COALESCE(ftcalc.live_total_charges, fs.folio_total_charges, 0) - {$extrasTotalExpr}, 0)
            WHEN GREATEST(
                COALESCE(NULLIF(rateov.calculated_room_total, 0), 0),
                COALESCE(NULLIF(o.totalamount, 0), 0),
                COALESCE(NULLIF(ch.accommodation_total, 0), 0),
                COALESCE(NULLIF(ch.room_charge_total, 0), 0)
            ) > 0
            THEN GREATEST(
                COALESCE(NULLIF(rateov.calculated_room_total, 0), 0),
                COALESCE(NULLIF(o.totalamount, 0), 0),
                COALESCE(NULLIF(ch.accommodation_total, 0), 0),
                COALESCE(NULLIF(ch.room_charge_total, 0), 0)
            )
            ELSE GREATEST(
                COALESCE(NULLIF(pmt.payment_table_total, 0), 0),
                COALESCE(NULLIF(pay.voided_payments, 0), 0),
                COALESCE(NULLIF(pay.gross_payment_credits, 0), 0)
            )
        END AS calculated_room_total,

        CASE
            WHEN COALESCE(ftcalc.live_transaction_count, fs.folio_count, 0) > 0
            THEN GREATEST(COALESCE(ftcalc.live_total_charges, fs.folio_total_charges, 0) - {$extrasTotalExpr}, 0)
            WHEN GREATEST(
                COALESCE(NULLIF(rateov.final_room_total, 0), 0),
                COALESCE(NULLIF(o.totalamount, 0), 0),
                COALESCE(NULLIF(ch.accommodation_total, 0), 0),
                COALESCE(NULLIF(ch.room_charge_total, 0), 0)
            ) > 0
            THEN GREATEST(
                COALESCE(NULLIF(rateov.final_room_total, 0), 0),
                COALESCE(NULLIF(o.totalamount, 0), 0),
                COALESCE(NULLIF(ch.accommodation_total, 0), 0),
                COALESCE(NULLIF(ch.room_charge_total, 0), 0)
            )
            ELSE GREATEST(
                COALESCE(NULLIF(pmt.payment_table_total, 0), 0),
                COALESCE(NULLIF(pay.voided_payments, 0), 0),
                COALESCE(NULLIF(pay.gross_payment_credits, 0), 0)
            )
        END AS final_room_total,

        COALESCE(rateov.override_types, '') AS override_types,
        {$extrasTotalExpr} AS extras_total,

        CASE
            WHEN COALESCE(ftcalc.live_transaction_count, fs.folio_count, 0) > 0
            THEN COALESCE(ftcalc.live_total_charges, fs.folio_total_charges, 0)
            ELSE (
                CASE
                    WHEN GREATEST(
                        COALESCE(NULLIF(rateov.final_room_total, 0), 0),
                        COALESCE(NULLIF(o.totalamount, 0), 0),
                        COALESCE(NULLIF(ch.accommodation_total, 0), 0),
                        COALESCE(NULLIF(ch.room_charge_total, 0), 0)
                    ) > 0
                    THEN GREATEST(
                        COALESCE(NULLIF(rateov.final_room_total, 0), 0),
                        COALESCE(NULLIF(o.totalamount, 0), 0),
                        COALESCE(NULLIF(ch.accommodation_total, 0), 0),
                        COALESCE(NULLIF(ch.room_charge_total, 0), 0)
                    )
                    ELSE GREATEST(
                        COALESCE(NULLIF(pmt.payment_table_total, 0), 0),
                        COALESCE(NULLIF(pay.voided_payments, 0), 0),
                        COALESCE(NULLIF(pay.gross_payment_credits, 0), 0)
                    )
                END + {$extrasTotalExpr}
            )
        END AS grand_total,
        COALESCE(pay.total_payments, 0) AS total_payments,
        COALESCE(ftcalc.live_total_balance, fs.folio_total_balance, pay.total_folio_balance, 0) AS folio_balance,
        COALESCE(pay.voided_payments, 0) AS voided_payments,
        o.status,
        o.booking_status,
        o.no_show_at,
        o.no_show_reason,
        o.no_show_policy_action,
        o.no_show_original_balance,
        o.no_show_adjustment_amount,
        o.no_show_policy_charge,
        o.no_show_final_balance,
        o.no_show_inventory_released,
        o.cancellation_at,
        o.cancellation_reason,
        o.cancellation_policy_action,
        o.cancellation_original_balance,
        o.cancellation_adjustment_amount,
        o.cancellation_policy_charge,
        o.cancellation_final_balance,
        o.cancellation_inventory_released,
        COALESCE(rstat.room_slots_total, 0) AS room_slots_total,
        COALESCE(rstat.room_slots_checked_in, 0) AS room_slots_checked_in,
        COALESCE(rstat.room_slots_checked_out, 0) AS room_slots_checked_out,
        o.payment_status AS stored_payment_status,
        o.room_type_id,
        o.ordered_on,
        o.updated_on,
        {$expectedArrivalSelectSql}
        {$createdBySelectSql}
        {$updatedBySelectSql}
        {$createdByNameSelectSql}
        {$updatedByNameSelectSql}
        o.contact_id,
        o.originating_agent,
        o.voucher_no,
        o.nationality_id,
        g.firstname,
        g.lastname,
        rmix.room_mix,
        COALESCE(rsetup.extra_beds_required, 0) AS extra_beds_required,
        COALESCE(rsetup.cots_required, 0) AS cots_required,
        COALESCE(rsetup.wheelchair_room_required, 0) AS wheelchair_room_required,
        COALESCE(rsetup.interleading_required, 0) AS interleading_required,
        COALESCE(rsetup.room_setup_notes, '') AS room_setup_notes,
        COALESCE(rsetup.housekeeping_notes, '') AS housekeeping_notes,
        COALESCE(rsetup.special_occasion, '') AS special_occasion,
        p.name AS property_name,
        p.code AS property_code,
        CONCAT(COALESCE(a.firstname,''), ' ') AS agent_name,
        CONCAT(COALESCE(c.firstname,''), ' ', COALESCE(c.lastname,'')) AS contact_name,
        n.nationality AS nationality_name
    FROM {$ordersSourceSql}
    LEFT JOIN guests g ON g.id = o.guest_id
    LEFT JOIN properties p ON p.id = o.property_id
    LEFT JOIN agents a ON a.id = o.agent_id
    LEFT JOIN consultants c ON c.id = o.contact_id
    {$createdByJoinSql}
    {$updatedByJoinSql}
    LEFT JOIN nationality n ON n.id = o.nationality_id

    {$overrideSelectSql}

    LEFT JOIN (
        SELECT
            order_id,
            COALESCE(SUM(total_charges), 0) AS folio_total_charges,
            COALESCE(SUM(total_payments), 0) AS folio_total_payments,
            COALESCE(SUM(balance), 0) AS folio_total_balance,
            COUNT(*) AS folio_count
        FROM folios
        GROUP BY order_id
    ) fs ON fs.order_id = o.id


    LEFT JOIN (
        /*
           Live folio totals from transactions.
           This fixes the reservation list total immediately after adding Extras,
           even before the Payments page recalculates/caches folio totals.
        */
        SELECT
            order_id,
            COALESCE(SUM(CASE WHEN COALESCE(is_voided, 0) = 0 THEN COALESCE(debit, 0) ELSE 0 END), 0) AS live_total_charges,
            COALESCE(SUM(CASE WHEN COALESCE(is_voided, 0) = 0 THEN COALESCE(credit, 0) ELSE 0 END), 0) AS live_total_payments,
            COALESCE(SUM(CASE WHEN COALESCE(is_voided, 0) = 0 THEN COALESCE(debit, 0) - COALESCE(credit, 0) ELSE 0 END), 0) AS live_total_balance,
            COUNT(*) AS live_transaction_count
        FROM folio_transactions
        GROUP BY order_id
    ) ftcalc ON ftcalc.order_id = o.id

    {$extraItemsJoinSql}

    LEFT JOIN (
        /*
           Accommodation/revenue charges only.
           Excludes payments, receipts, payment voids, reversals, refunds and extras.
           This is used only as a safe fallback when orders.totalamount or nightly prices are missing/zero.
        */
        SELECT
            ft.order_id,
            GREATEST(
                0,
                COALESCE(SUM(
                    CASE
                        WHEN COALESCE(ft.is_voided, 0) = 0
                             AND NOT (
                                LOWER(COALESCE(ft.tx_type, '')) IN (
                                    'payment','receipt','deposit','payment_received',
                                    'folio_payment','guest_payment',
                                    'payment_void','void_payment','payment_reversal',
                                    'reversal','refund','payment_refund'
                                )
                                OR LOWER(COALESCE(ft.source_table, '')) IN (
                                    'payments','payment','receipts','receipt'
                                )
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%void%'
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%reversal%'
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%refund%'
                             )
                             AND NOT (
                                LOWER(COALESCE(ft.tx_type, '')) IN (
                                    'extra', 'extras', 'extra_charge', 'addon', 'add_on',
                                    'transfer', 'restaurant', 'minibar', 'laundry',
                                    'park_fee', 'activity', 'service', 'bar', 'curio', 'spar', 'spa'
                                )
                                OR LOWER(COALESCE(ft.source_table, '')) IN (
                                    'tblservices', 'services', 'extras', 'order_extras',
                                    'booking_extras', 'reservation_extras',
                                    'order_extra_items', 'manual_extra', 'extra_charge_items',
                                    'restaurant_menu_items', 'inventory_items'
                                )
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%extra%'
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%transfer%'
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%restaurant%'
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%minibar%'
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%laundry%'
                             )
                        THEN COALESCE(ft.debit, 0) - COALESCE(ft.credit, 0)
                        ELSE 0
                    END
                ), 0)
            ) AS accommodation_total,

            COALESCE(SUM(
                CASE
                    WHEN LOWER(COALESCE(ft.tx_type, '')) = 'room_charge'
                    THEN COALESCE(ft.debit, 0) - COALESCE(ft.credit, 0)
                    ELSE 0
                END
            ), 0) AS room_charge_total
        FROM folio_transactions ft
        GROUP BY ft.order_id
    ) ch ON ch.order_id = o.id

    LEFT JOIN (
        SELECT
            order_id,
            COALESCE(SUM(ABS(COALESCE(amount, 0))), 0) AS payment_table_total
        FROM payment
        GROUP BY order_id
    ) pmt ON pmt.order_id = o.id

    LEFT JOIN (
        SELECT
            ft.order_id,
            SUM(
                CASE
                    WHEN COALESCE(ft.is_voided, 0) = 0
                         AND (
                            LOWER(COALESCE(ft.tx_type, '')) IN (
                                'extra', 'extras', 'extra_charge', 'addon', 'add_on',
                                'transfer', 'restaurant', 'minibar', 'laundry',
                                'park_fee', 'activity', 'service', 'bar', 'curio'
                            )
                            OR LOWER(COALESCE(ft.source_table, '')) IN (
                                'tblservices', 'services', 'extras', 'order_extras',
                                'booking_extras', 'reservation_extras',
                                'order_extra_items', 'manual_extra'
                            )
                            OR LOWER(COALESCE(ft.description, '')) LIKE '%extra%'
                            OR LOWER(COALESCE(ft.description, '')) LIKE '%transfer%'
                            OR LOWER(COALESCE(ft.description, '')) LIKE '%restaurant%'
                            OR LOWER(COALESCE(ft.description, '')) LIKE '%minibar%'
                            OR LOWER(COALESCE(ft.description, '')) LIKE '%laundry%'
                         )
                    THEN (COALESCE(ft.debit, 0) - COALESCE(ft.credit, 0))
                    ELSE 0
                END
            ) AS extras_total
        FROM folio_transactions ft
        GROUP BY ft.order_id
    ) ex ON ex.order_id = o.id

    LEFT JOIN (
        /*
           Net payment must be based on payment-type transactions only.

           This supports both reversal styles:
           1) Original payment row is marked is_voided = 1
           2) System posts a separate void/reversal row with debit > 0
              or negative credit.

           Result:
           paid 100 then void 100 => net_paid = 0
        */
        SELECT
            ft.order_id,

            GREATEST(
                0,
                COALESCE(SUM(
                    CASE
                        WHEN COALESCE(ft.is_voided, 0) = 0
                             AND (
                                LOWER(COALESCE(ft.tx_type, '')) IN (
                                    'payment','receipt','deposit','payment_received',
                                    'folio_payment','guest_payment'
                                )
                                OR LOWER(COALESCE(ft.source_table, '')) IN (
                                    'payments','payment','receipts','receipt'
                                )
                             )
                             AND COALESCE(ft.credit, 0) > 0
                        THEN COALESCE(ft.credit, 0)

                        WHEN COALESCE(ft.is_voided, 0) = 0
                             AND (
                                LOWER(COALESCE(ft.tx_type, '')) IN (
                                    'payment_void','void_payment','payment_reversal',
                                    'reversal','refund','payment_refund'
                                )
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%void%'
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%reversal%'
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%refund%'
                             )
                             AND COALESCE(ft.debit, 0) > 0
                        THEN -1 * COALESCE(ft.debit, 0)

                        WHEN COALESCE(ft.is_voided, 0) = 0
                             AND (
                                LOWER(COALESCE(ft.tx_type, '')) IN (
                                    'payment_void','void_payment','payment_reversal',
                                    'reversal','refund','payment_refund'
                                )
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%void%'
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%reversal%'
                                OR LOWER(COALESCE(ft.description, '')) LIKE '%refund%'
                             )
                             AND COALESCE(ft.credit, 0) < 0
                        THEN COALESCE(ft.credit, 0)

                        ELSE 0
                    END
                ), 0)
            ) AS total_payments,

            COALESCE(SUM(
                CASE
                    WHEN LOWER(COALESCE(ft.tx_type, '')) IN (
                        'payment','receipt','deposit','payment_received',
                        'folio_payment','guest_payment'
                    )
                    OR LOWER(COALESCE(ft.source_table, '')) IN (
                        'payments','payment','receipts','receipt'
                    )
                    THEN ABS(COALESCE(ft.credit, 0))
                    ELSE 0
                END
            ), 0) AS gross_payment_credits,

            COALESCE(SUM(
                CASE
                    WHEN COALESCE(ft.is_voided, 0) = 0
                    THEN COALESCE(ft.debit, 0) - COALESCE(ft.credit, 0)
                    ELSE 0
                END
            ), 0) AS total_folio_balance,

            COALESCE(SUM(
                CASE
                    WHEN COALESCE(ft.is_voided, 0) = 1
                         AND COALESCE(ft.credit, 0) > 0
                    THEN COALESCE(ft.credit, 0)

                    WHEN COALESCE(ft.is_voided, 0) = 0
                         AND (
                            LOWER(COALESCE(ft.tx_type, '')) IN (
                                'payment_void','void_payment','payment_reversal',
                                'reversal','refund','payment_refund'
                            )
                            OR LOWER(COALESCE(ft.description, '')) LIKE '%void%'
                            OR LOWER(COALESCE(ft.description, '')) LIKE '%reversal%'
                            OR LOWER(COALESCE(ft.description, '')) LIKE '%refund%'
                         )
                    THEN ABS(COALESCE(ft.debit, 0)) + ABS(LEAST(COALESCE(ft.credit, 0), 0))

                    ELSE 0
                END
            ), 0) AS voided_payments

        FROM folio_transactions ft
        GROUP BY ft.order_id
    ) pay ON pay.order_id = o.id

    LEFT JOIN (
        SELECT
            order_id,
            COUNT(*) AS room_slots_total,
            SUM(CASE WHEN COALESCE(checked_in_status, 0) = 1 THEN 1 ELSE 0 END) AS room_slots_checked_in,
            SUM(CASE WHEN COALESCE(checked_out_status, 0) = 1 THEN 1 ELSE 0 END) AS room_slots_checked_out
        FROM order_rooming
        GROUP BY order_id
    ) rstat ON rstat.order_id = o.id

    LEFT JOIN (
        SELECT
            order_id,
            GROUP_CONCAT(room_summary ORDER BY sort_title SEPARATOR ', ') AS room_mix
        FROM (
            SELECT
                rmg.order_id,
                rt.title AS sort_title,
                CONCAT(
                    rt.title,
                    ' x ',
                    COUNT(*),
                    CASE
                        WHEN SUM(
                            CASE
                                WHEN (COALESCE(rmg.adults, 0) + COALESCE(rmg.children, 0)) = 3
                                     AND LOWER(COALESCE(rt.title, '')) NOT LIKE '%family%'
                                THEN 1
                                ELSE 0
                            END
                        ) > 0
                        THEN CONCAT(
                            ' (',
                            SUM(
                                CASE
                                    WHEN (COALESCE(rmg.adults, 0) + COALESCE(rmg.children, 0)) = 3
                                         AND LOWER(COALESCE(rt.title, '')) NOT LIKE '%family%'
                                    THEN 1
                                    ELSE 0
                                END
                            ),
                            ' Triple / Extra Bed)'
                        )
                        ELSE ''
                    END
                ) AS room_summary
            FROM order_rooming rmg
            INNER JOIN room_types rt ON rt.id = rmg.room_type_id
            GROUP BY rmg.order_id, rmg.room_type_id, rt.title
        ) x
        GROUP BY order_id
    ) rmix ON rmix.order_id = o.id

    LEFT JOIN (
        SELECT
            order_id,
            {$roomExtraBedsExpr} AS extra_beds_required,
            {$roomCotsExpr} AS cots_required,
            {$roomWheelchairExpr} AS wheelchair_room_required,
            {$roomInterleadingExpr} AS interleading_required,
            {$roomSetupNotesExpr} AS room_setup_notes,
            {$roomHousekeepingNotesExpr} AS housekeeping_notes,
            {$roomSpecialOccasionExpr} AS special_occasion
        FROM order_rooming
        GROUP BY order_id
    ) rsetup ON rsetup.order_id = o.id

    WHERE 1=1
";

$params = $candidateParams;

if ($filterProperty !== '' && (int)$filterProperty > 0) {
    $sql .= " AND o.property_id = :property_id ";
    $params[':property_id'] = (int)$filterProperty;
}

if ($filterFrom !== '') {
    $sql .= " AND o.check_in >= :from_date ";
    $params[':from_date'] = $filterFrom;
}

if ($filterTo !== '') {
    $sql .= " AND o.check_out <= :to_date ";
    $params[':to_date'] = $filterTo;
}

if ($filterStatus !== '') {
    $noShowExclusionSql = " AND LOWER(COALESCE(o.booking_status,'')) NOT IN ('no_show','noshow') AND o.no_show_at IS NULL ";

    if ($filterStatus === 'no_show') {
        $sql .= " AND (LOWER(COALESCE(o.booking_status,'')) IN ('no_show','noshow') OR o.no_show_at IS NOT NULL) ";
    } elseif ($filterStatus === '3') {
        $sql .= " AND COALESCE(rstat.room_slots_checked_in, 0) > 0 AND NOT (COALESCE(rstat.room_slots_total, 0) > 0 AND COALESCE(rstat.room_slots_checked_out, 0) >= COALESCE(rstat.room_slots_total, 0)) " . $noShowExclusionSql;
    } elseif ($filterStatus === '4') {
        $sql .= " AND COALESCE(rstat.room_slots_total, 0) > 0 AND COALESCE(rstat.room_slots_checked_out, 0) >= COALESCE(rstat.room_slots_total, 0) " . $noShowExclusionSql;
    } elseif ($filterStatus === '0') {
        $sql .= " AND o.status = :status ";
        $params[':status'] = $filterStatus;
    } else {
        $sql .= " AND o.status = :status " . $noShowExclusionSql;
        $params[':status'] = $filterStatus;
    }
}

/*
   Search text has already been applied inside the candidate orders subquery.
   Do not repeat the broad LIKE conditions here; doing so forces MySQL to
   evaluate concatenated and grouped fields after all expensive joins.
*/

/*
   Preserve the existing newest-first ordering.
   The unfiltered 30-row limit is already applied inside $ordersSourceSql,
   before the outer joins are evaluated.
*/
$sql .= " ORDER BY o.id DESC ";

$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

/*
   Cancellation / No-show strategy:
   These policy workflows use clean folio accounting.
   Reservation List must show the same amount as Front Desk: the live final folio balance after
   adjustment credits and policy charges, not gross debit-only charges.
*/
foreach ($rows as &$rlRow) {
    $bookingStatus = strtolower(trim((string)($rlRow['booking_status'] ?? '')));
    $isNoShowRow = in_array($bookingStatus, ['no_show', 'noshow'], true) || !empty($rlRow['no_show_at']);

    if ((string)($rlRow['status'] ?? '') === '0') {
        $finalCancelBalance = (float)($rlRow['cancellation_final_balance'] ?? 0);
        if (abs($finalCancelBalance) <= 0.00001) {
            $finalCancelBalance = (float)($rlRow['folio_balance'] ?? 0);
        }
        $rlRow['grand_total'] = $finalCancelBalance;
        $rlRow['final_room_total'] = $finalCancelBalance;
        $rlRow['calculated_room_total'] = (float)($rlRow['cancellation_original_balance'] ?? $rlRow['calculated_room_total'] ?? 0);
        $rlRow['extras_total'] = 0;
    } elseif ($isNoShowRow) {
        $finalNoShowBalance = (float)($rlRow['no_show_final_balance'] ?? 0);
        if (abs($finalNoShowBalance) <= 0.00001) {
            $finalNoShowBalance = (float)($rlRow['folio_balance'] ?? 0);
        }

        // This is the same figure Front Desk shows as Folio Balance.
        $rlRow['grand_total'] = $finalNoShowBalance;
        $rlRow['final_room_total'] = $finalNoShowBalance;
        $rlRow['calculated_room_total'] = (float)($rlRow['no_show_original_balance'] ?? $rlRow['calculated_room_total'] ?? 0);
        $rlRow['extras_total'] = 0;
    }
}
unset($rlRow);

$totalBookings = count($rows);
$totalValue = 0.0;
$totalRoomsBooked = 0;
$inHouse = 0;
$arrivals = 0;
$departures = 0;
$today = date('Y-m-d');

foreach ($rows as $row) {
    $totalValue += (float)($row['grand_total'] ?? 0);
    $totalRoomsBooked += (int)($row['totalrooms'] ?? 1);

    if (!empty($row['check_in']) && !empty($row['check_out'])) {
        $effectiveMetricStatus = reservation_frontdesk_status_value($row);
        if ($row['check_in'] <= $today && $row['check_out'] > $today && $effectiveMetricStatus === '3') {
            $inHouse++;
        }
        if ($row['check_in'] === $today) {
            $arrivals++;
        }
        if ($row['check_out'] === $today) {
            $departures++;
        }
    }
}

require_once __DIR__ . '/../../includes/header.php';
?>

<style>
.reservation-dashboard {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1rem;
}

.table-shell {
    border-radius: 24px;
    overflow: hidden;
    background: #fff;
    box-shadow: 0 22px 60px rgba(15, 23, 42, 0.10);
    border: 1px solid #eef2f7;
}

.table-responsive {
    border-radius: 20px;
    overflow: auto;
}

.table-modern {
    border-collapse: separate;
    border-spacing: 0;
    font-size: .86rem;
}

.table-modern thead th {
    position: sticky;
    top: 0;
    z-index: 8;
    background: linear-gradient(180deg, #ffffff, #f8fafc);
    color: #64748b;
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .07em;
    font-weight: 800;
    padding: .85rem .75rem;
    border-bottom: 1px solid #e5e7eb;
    white-space: nowrap;
}

.table-modern tbody td {
    padding: .62rem .75rem;
    border-bottom: 1px solid #eef2f7;
    vertical-align: middle;
}

.table-modern tbody tr {
    cursor: pointer;
    transition: all .18s ease;
}

.table-modern tbody tr:nth-child(odd) {
    background: #ffffff;
}

.table-modern tbody tr:nth-child(even) {
    background: #fbfdff;
}

.table-modern tbody tr:hover {
    background: #f0f7ff !important;
    box-shadow: inset 5px 0 0 #ff385c, 0 12px 26px rgba(15, 23, 42, .08);
}

.row-status-1 { box-shadow: inset 5px 0 0 #198754; }
.row-status-2 { box-shadow: inset 5px 0 0 #ffc107; }
.row-status-3 { box-shadow: inset 5px 0 0 #0d6efd; }
.row-status-4 { box-shadow: inset 5px 0 0 #6c757d; }
.row-status-0 {
    background: repeating-linear-gradient(
        -45deg,
        #fff1f2 0,
        #fff1f2 10px,
        #ffe4e6 10px,
        #ffe4e6 20px
    ) !important;
    box-shadow: inset 5px 0 0 #dc3545;
    color: #7f1d1d;
}

.row-status-noshow,
.row-no-show {
    background: repeating-linear-gradient(
        -45deg,
        #fff7ed 0,
        #fff7ed 10px,
        #ffedd5 10px,
        #ffedd5 20px
    ) !important;
    box-shadow: inset 5px 0 0 #ea580c;
    color: #7c2d12;
}
.no-show-ribbon {
    background: #ea580c !important;
    box-shadow: 0 10px 22px rgba(234, 88, 12, .22) !important;
}

.row-cancelled {
    position: relative;
    opacity: .78;
}

.row-cancelled::after {
    content: "CANCELLED";
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%) rotate(-8deg);
    font-size: 1.65rem;
    font-weight: 950;
    letter-spacing: .22em;
    color: rgba(220, 53, 69, .13);
    pointer-events: none;
    z-index: 2;
    white-space: nowrap;
}

.row-cancelled .data-stack strong,
.row-cancelled .amount-main,
.row-cancelled .booking-ref {
    text-decoration: line-through;
    text-decoration-thickness: 2px;
    text-decoration-color: rgba(220, 53, 69, .65);
}

.row-cancelled td {
    position: relative;
}

.cancelled-ribbon {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .28rem .65rem;
    border-radius: 999px;
    background: #dc3545;
    color: #fff;
    font-size: .70rem;
    font-weight: 900;
    letter-spacing: .05em;
    box-shadow: 0 10px 22px rgba(220, 53, 69, .22);
}

.cancelled-lock-note {
    display: block;
    margin-top: .25rem;
    font-size: .68rem;
    color: #991b1b;
    font-weight: 700;
}

.table-modern th:last-child,
.table-modern td:last-child {
    position: sticky;
    right: 0;
    z-index: 5;
    background: inherit;
    box-shadow: -12px 0 24px rgba(15, 23, 42, .05);
}

.airbnb-action {
    border-radius: 999px;
    font-weight: 800;
    padding: .38rem .78rem;
    background: #ff385c;
    border-color: #ff385c;
}

.airbnb-action:hover {
    background: #e03150;
    border-color: #e03150;
}

.quick-select {
    min-width: 118px;
    border-radius: 999px;
    font-size: .76rem;
    font-weight: 700;
    padding: .28rem 1.8rem .28rem .65rem;
    cursor: pointer;
}

.data-stack strong {
    font-size: .84rem;
    font-weight: 700;
    color: #0f172a;
}

.data-stack small {
    display: block;
    font-size: .72rem;
    color: #64748b;
}

.booking-ref {
    font-size: .76rem;
    font-weight: 700;
    background: #f1f5f9;
    color: #334155;
    border-radius: 999px;
    padding: .28rem .55rem;
    white-space: nowrap;
}

.triple-room-note {
    display: inline-flex;
    align-items: center;
    gap: .28rem;
    margin-top: .35rem;
    padding: .22rem .55rem;
    border-radius: 999px;
    background: #fff7ed;
    color: #9a3412;
    border: 1px solid #fed7aa;
    font-size: .69rem;
    font-weight: 800;
    width: fit-content;
}

.room-setup-note {
    display: inline-flex;
    align-items: center;
    gap: .28rem;
    margin-top: .35rem;
    padding: .22rem .55rem;
    border-radius: 999px;
    background: #ecfeff;
    color: #155e75;
    border: 1px solid #a5f3fc;
    font-size: .69rem;
    font-weight: 800;
    max-width: 240px;
    cursor: help;
}
.room-setup-note .room-setup-note-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}


.rate-override-note {
    display: inline-flex;
    align-items: center;
    gap: .28rem;
    margin-top: .35rem;
    padding: .22rem .55rem;
    border-radius: 999px;
    background: #eef2ff;
    color: #3730a3;
    border: 1px solid #c7d2fe;
    font-size: .69rem;
    font-weight: 800;
    width: fit-content;
}

.amount-main {
    font-weight: 900;
    color: #0f172a;
}

.amount-sub {
    font-size: .69rem;
    color: #64748b;
}

.dropdown-menu {
    border: 0;
    border-radius: 16px;
    padding: .45rem;
    box-shadow: 0 18px 45px rgba(15, 23, 42, .16);
    font-size: .86rem;
}

.dropdown-item {
    border-radius: 12px;
    padding: .55rem .8rem;
    font-weight: 500;
}

.dropdown-item:hover {
    background: #f1f5f9;
}

.dropdown-item.text-danger:hover {
    background: #fff1f2;
}

.preview-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, .35);
    z-index: 1040;
    opacity: 0;
    visibility: hidden;
    transition: .2s ease;
}

.preview-backdrop.show {
    opacity: 1;
    visibility: visible;
}

.preview-panel {
    position: fixed;
    top: 0;
    right: -500px;
    width: min(500px, 96vw);
    height: 100vh;
    background: #ffffff;
    z-index: 1050;
    box-shadow: -30px 0 80px rgba(15, 23, 42, .22);
    transition: right .25s ease;
    display: flex;
    flex-direction: column;
}

.preview-panel.show {
    right: 0;
}

.preview-hero {
    padding: 1.4rem;
    background: linear-gradient(135deg, #111827, #1f2937);
    color: #fff;
}

.preview-panel.preview-cancelled .preview-hero {
    background: linear-gradient(135deg, #7f1d1d, #dc3545);
}

.preview-panel.preview-cancelled .preview-hero::after {
    content: "CANCELLED";
    display: inline-flex;
    margin-top: .75rem;
    padding: .25rem .65rem;
    border-radius: 999px;
    background: rgba(255,255,255,.16);
    border: 1px solid rgba(255,255,255,.32);
    color: #fff;
    font-size: .72rem;
    font-weight: 900;
    letter-spacing: .12em;
}

.preview-body {
    padding: 1.25rem;
    overflow: auto;
}

.preview-pill {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .35rem .65rem;
    border-radius: 999px;
    background: #f1f5f9;
    color: #334155;
    font-size: .75rem;
    font-weight: 700;
}

.preview-stat {
    border: 1px solid #eef2f7;
    border-radius: 18px;
    padding: .9rem;
    background: #fbfdff;
}

.preview-stat small {
    color: #64748b;
    font-weight: 700;
}

.preview-stat strong {
    display: block;
    font-size: 1rem;
    margin-top: .2rem;
}

.inline-saving {
    opacity: .6;
    pointer-events: none;
}

@media (max-width: 991px) {
    .table-modern th,
    .table-modern td {
        white-space: nowrap;
    }
}
/* ACTION DROPDOWN FIX */
.action-dropdown {
    position: relative;
}

.action-dropdown .dropdown-menu {
    z-index: 99999 !important;
    min-width: 220px;
    background: #ffffff !important;
    color: #111827 !important;
    border: 1px solid #e5e7eb !important;
}

.action-dropdown .dropdown-item {
    color: #111827 !important;
    background: transparent;
    white-space: nowrap;
}

.action-dropdown .dropdown-item:hover {
    background: #f1f5f9 !important;
    color: #111827 !important;
}

.action-dropdown .dropdown-divider {
    border-top-color: #e5e7eb;
}

/* Stop sticky action cell from covering dropdown text */
.table-modern th:last-child,
.table-modern td:last-child {
    z-index: 20;
}

.table-modern td:last-child .dropdown-menu {
    z-index: 99999 !important;
}


/* COMPACT RESERVATION LIST UX + FROZEN HEADER */
.hero-band {
    padding: .85rem 1.1rem !important;
    margin-bottom: .65rem !important;
    border-radius: 18px !important;
    min-height: auto !important;
}
.hero-band .row {
    --bs-gutter-y: .55rem;
}
.hero-title {
    font-size: 1.35rem !important;
    line-height: 1.05 !important;
    margin-top: .1rem !important;
}
.hero-subtitle {
    display: none !important;
}
.page-eyebrow {
    font-size: .70rem !important;
    letter-spacing: .08em !important;
}
.hero-band .btn {
    padding: .42rem .78rem !important;
    font-size: .82rem !important;
    border-radius: 999px !important;
}
.compact-metrics-row .metric-card {
    padding: .62rem .78rem !important;
    min-height: auto !important;
    border-radius: 16px !important;
    box-shadow: 0 8px 22px rgba(15, 23, 42, .06) !important;
}
.compact-metrics-row .metric-icon {
    width: 34px !important;
    height: 34px !important;
    font-size: .95rem !important;
    border-radius: 12px !important;
}
.compact-metrics-row .metric-label {
    font-size: .68rem !important;
    line-height: 1.05 !important;
}
.compact-metrics-row .metric-value {
    font-size: 1.02rem !important;
    line-height: 1.05 !important;
}
.compact-kpi-strip {
    padding: 0 !important;
}
.compact-kpi-strip .kpi-pill {
    padding: .32rem .58rem !important;
    font-size: .74rem !important;
}
.compact-filter-card {
    border-radius: 18px !important;
}
.compact-filter-card .card-header {
    padding: .58rem .85rem !important;
    min-height: auto !important;
}
.compact-filter-card .card-header h5 {
    font-size: .95rem !important;
}
.compact-filter-card .card-body {
    padding: .75rem .85rem !important;
}
.compact-filter-card .form-label {
    font-size: .72rem !important;
    margin-bottom: .18rem !important;
    font-weight: 700 !important;
}
.compact-filter-card .form-control,
.compact-filter-card .form-select,
.compact-filter-card .btn {
    min-height: 34px !important;
    padding-top: .32rem !important;
    padding-bottom: .32rem !important;
    font-size: .82rem !important;
}
.compact-filter-card form.row {
    --bs-gutter-y: .45rem;
}
.compact-filter-card .col-md-12 {
    display: none !important;
}
.reservation-list-card {
    border-radius: 18px !important;
    overflow: hidden;
}
.compact-list-header {
    padding: .58rem .85rem !important;
    min-height: auto !important;
}
.compact-list-header h5 {
    font-size: .98rem !important;
}
.compact-list-header small {
    display: none !important;
}
.table-shell {
    border-radius: 18px !important;
    overflow: hidden !important;
}
.reservation-list-scroll {
    max-height: calc(100vh - 245px);
    overflow: auto !important;
    border-radius: 0 0 18px 18px !important;
    position: relative;
}
.table-modern thead th {
    position: sticky !important;
    top: 0 !important;
    z-index: 80 !important;
    padding: .56rem .62rem !important;
    background: #ffffff !important;
    box-shadow: 0 2px 0 #e5e7eb, 0 8px 18px rgba(15,23,42,.06) !important;
}
.table-modern tbody td {
    padding: .48rem .62rem !important;
}
.table-modern th:last-child {
    z-index: 90 !important;
    background: #ffffff !important;
}
.table-modern td:last-child {
    z-index: 25 !important;
}
.data-stack strong {
    font-size: .80rem !important;
}
.data-stack small,
.amount-sub {
    font-size: .66rem !important;
}
.airbnb-action {
    padding: .30rem .62rem !important;
    font-size: .76rem !important;
}
.quick-select {
    min-width: 106px !important;
    font-size: .72rem !important;
    padding-top: .22rem !important;
    padding-bottom: .22rem !important;
}
@media (max-height: 760px) {
    .reservation-list-scroll {
        max-height: calc(100vh - 205px);
    }
    .compact-metrics-row,
    .compact-kpi-strip {
        display: none !important;
    }
}


/* Nested Proforma submenu inside Actions */
.action-dropdown .dropdown-submenu {
    position: relative;
}

.action-dropdown .dropdown-submenu > .dropdown-menu {
    position: absolute;
    top: 0;
    right: 100%;
    left: auto;
    margin-top: 0;
    margin-right: .25rem;
    min-width: 235px;
    display: none;
    z-index: 100000 !important;
}

.action-dropdown .dropdown-submenu:hover > .dropdown-menu,
.action-dropdown .dropdown-submenu:focus-within > .dropdown-menu {
    display: block;
}

.action-dropdown .proforma-submenu > .dropdown-item {
    cursor: default;
}

@media (max-width: 991px) {
    .action-dropdown .dropdown-submenu > .dropdown-menu {
        position: static;
        display: none;
        margin: .25rem 0 .25rem .75rem;
        box-shadow: none;
        border: 1px solid #e5e7eb !important;
    }

    .action-dropdown .dropdown-submenu:hover > .dropdown-menu,
    .action-dropdown .dropdown-submenu:focus-within > .dropdown-menu {
        display: block;
    }
}

/* Portalled Actions menu (moved to <body> to escape the table's overflow
   clipping) - re-declare styling and submenu behaviour outside .action-dropdown. */
.dropdown-menu.action-menu-portalled { z-index: 200000 !important; }
.dropdown-menu.action-menu-portalled .dropdown-submenu { position: relative; }
.dropdown-menu.action-menu-portalled .dropdown-submenu > .dropdown-menu {
    position: absolute; top: 0; right: 100%; left: auto;
    margin-top: 0; margin-right: .25rem; min-width: 235px;
    display: none; z-index: 200001 !important;
}
.dropdown-menu.action-menu-portalled .dropdown-submenu:hover > .dropdown-menu,
.dropdown-menu.action-menu-portalled .dropdown-submenu:focus-within > .dropdown-menu { display: block; }
@media (max-width: 991px) {
    .dropdown-menu.action-menu-portalled .dropdown-submenu > .dropdown-menu {
        position: static; display: none; margin: .25rem 0 .25rem .75rem;
        box-shadow: none; border: 1px solid #e5e7eb !important;
    }
    .dropdown-menu.action-menu-portalled .dropdown-submenu:hover > .dropdown-menu,
    .dropdown-menu.action-menu-portalled .dropdown-submenu:focus-within > .dropdown-menu { display: block; }
}

</style>

<div class="hero-band">
    <div class="row g-4 align-items-center">
        <div class="col-lg-8">
            <div class="page-eyebrow text-white-50">
                <i class="bi bi-journal-check"></i> Reservation control
            </div>
            <div class="hero-title mt-2">Reservations</div>
            <div class="hero-subtitle mt-2">
                Manage reservations by property, room mix, total rooms, adults, children, pricing totals, operational status, and waitlist alerts.
            </div>
        </div>
        <div class="col-lg-4 text-lg-end">
            <div class="d-inline-flex gap-2 flex-wrap justify-content-lg-end">
                <a class="btn btn-light" href="/rooms/modules/reservations/reservation_form.php">
                    <i class="bi bi-plus-circle me-2"></i>New Reservation
                </a>

                <a class="btn btn-outline-danger position-relative" href="/rooms/modules/reservations/waitlist.php">
                    <i class="bi bi-hourglass-split me-2"></i>Waitlist
                    <?php if ($waitlistCount > 0): ?>
                        <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
                            <?php echo (int)$waitlistCount; ?>
                        </span>
                    <?php endif; ?>
                </a>
            </div>
        </div>
    </div>
</div>

<?php if (!empty($cancellationSuccess)): ?>
    <div class="alert alert-success"><?php echo h($cancellationSuccess); ?></div>
<?php endif; ?>

<?php if (!empty($cancellationErrors)): ?>
    <div class="alert alert-danger">
        <div class="fw-bold mb-1">Cancellation failed</div>
        <?php foreach ($cancellationErrors as $cancelErr): ?>
            <div><?php echo h($cancelErr); ?></div>
        <?php endforeach; ?>
    </div>
<?php endif; ?>

<div class="row g-2 mb-2 compact-metrics-row">
    <div class="col-sm-6 col-xl-3">
        <div class="metric-card">
            <div class="metric-icon bg-primary-subtle text-primary">
                <i class="bi bi-journal-check"></i>
            </div>
            <div>
                <div class="metric-label">Bookings</div>
                <div class="metric-value"><?php echo number_format($totalBookings); ?></div>
            </div>
        </div>
    </div>

    <div class="col-sm-6 col-xl-3">
        <div class="metric-card">
            <div class="metric-icon bg-success-subtle text-success">
                <i class="bi bi-door-open"></i>
            </div>
            <div>
                <div class="metric-label">Rooms Booked</div>
                <div class="metric-value"><?php echo number_format($totalRoomsBooked); ?></div>
            </div>
        </div>
    </div>

    <div class="col-sm-6 col-xl-3">
        <div class="metric-card">
            <div class="metric-icon bg-info-subtle text-info">
                <i class="bi bi-box-arrow-in-right"></i>
            </div>
            <div>
                <div class="metric-label">Arrivals Today</div>
                <div class="metric-value"><?php echo number_format($arrivals); ?></div>
            </div>
        </div>
    </div>

    <div class="col-sm-6 col-xl-3">
        <div class="metric-card">
            <div class="metric-icon bg-warning-subtle text-warning">
                <i class="bi bi-cash-stack"></i>
            </div>
            <div>
                <div class="metric-label">Total Value</div>
                <div class="metric-value"><?php echo display_money_local($totalValue); ?></div>
            </div>
        </div>
    </div>
</div>

<div class="kpi-strip compact-kpi-strip mb-2 d-flex flex-wrap gap-2">
    <span class="kpi-pill"><i class="bi bi-house-check text-primary"></i> <?php echo number_format($inHouse); ?> in house</span>
    <span class="kpi-pill"><i class="bi bi-box-arrow-right text-primary"></i> <?php echo number_format($departures); ?> departures today</span>
    <span class="kpi-pill"><i class="bi bi-hourglass-split text-danger"></i> <?php echo number_format($waitlistCount); ?> waitlist alert(s)</span>
</div>

<div class="card panel-card compact-filter-card mb-2">
    <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
        <div>
            <h5 class="mb-0">Filters</h5>
        </div>
        <div class="d-flex gap-2 flex-wrap">
            <a href="/rooms/modules/reservations/waitlist.php" class="btn btn-sm btn-outline-danger">
                <i class="bi bi-hourglass-split me-1"></i>Open Waitlist
            </a>
        </div>
    </div>
    <div class="card-body">
        <form method="get" class="row g-3 align-items-end">
            <div class="col-md-3">
                <label class="form-label">Property</label>
                <select name="property_id" class="form-select">
                    <option value="">All properties</option>
                    <?php foreach ($properties as $property): ?>
                        <option value="<?php echo (int)$property['id']; ?>" <?php echo (int)$filterProperty === (int)$property['id'] ? 'selected' : ''; ?>>
                            <?php echo h($property['name']); ?> - <?php echo h($property['code']); ?>
                        </option>
                    <?php endforeach; ?>
                </select>
            </div>

            <div class="col-md-2">
                <label class="form-label">From</label>
                <input type="date" name="from" class="form-control" value="<?php echo h($filterFrom); ?>">
            </div>

            <div class="col-md-2">
                <label class="form-label">To</label>
                <input type="date" name="to" class="form-control" value="<?php echo h($filterTo); ?>">
            </div>

            <div class="col-md-2">
                <label class="form-label">Status</label>
                <select name="status" class="form-select">
                    <option value="">All</option>
                    <option value="1" <?php echo $filterStatus === '1' ? 'selected' : ''; ?>>Confirmed</option>
                    <option value="2" <?php echo $filterStatus === '2' ? 'selected' : ''; ?>>Provisional</option>
                    <option value="3" <?php echo $filterStatus === '3' ? 'selected' : ''; ?>>In House</option>
                    <option value="4" <?php echo $filterStatus === '4' ? 'selected' : ''; ?>>Checked Out</option>
                    <option value="no_show" <?php echo $filterStatus === 'no_show' ? 'selected' : ''; ?>>No Show</option>
                    <option value="0" <?php echo $filterStatus === '0' ? 'selected' : ''; ?>>Cancelled</option>
                </select>
            </div>

            <div class="col-md-2">
                <label class="form-label">Search</label>
                <input type="text" name="q" class="form-control" value="<?php echo h($search); ?>" placeholder="Booking, guest, voucher...">
            </div>

            <div class="col-md-1 d-grid">
                <button class="btn btn-primary">Go</button>
            </div>

            <div class="col-md-12">
                <a href="/rooms/modules/reservations/reservation_list.php" class="btn btn-light">
                    Reset Filters
                </a>
            </div>
        </form>
    </div>
</div>

<div class="card panel-card reservation-list-card">
    <div class="card-header compact-list-header d-flex justify-content-between align-items-center">
        <div>
            <h5 class="mb-0">Reservation List</h5>
            <small>Click a row to preview. Operational status is editable; payment status updates automatically from recorded payments.</small>
        </div>
    </div>

    <div class="card-body p-0">
        <?php if (!$hasFilters): ?>
            <div class="alert alert-info m-3 mb-0 d-flex align-items-center gap-2" role="alert">
                <i class="bi bi-info-circle"></i>
                <div>Showing latest 500 reservations for faster loading. Use Search or Filters to find older bookings.</div>
            </div>
        <?php endif; ?>
        <div class="table-shell">
            <div class="table-responsive reservation-list-scroll">
                <table class="table table-modern align-middle mb-0">
                    <thead>
                        <tr>
                            <th>Booking</th>
                            <th>Property</th>
                            <th>Guest</th>
                            <th>Created By</th>
                            <th>Agent</th>
                            <th>Nationality</th>
                            <th>Stay</th>
                            <th>Nights</th>
                            <th>Room Mix</th>
                            <th>Rooms</th>
                            <th>Pax</th>
                            <th class="text-end">Total</th>
                            <th>Payment</th>
                            <th>Status</th>
                            <th class="text-end">Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php if (!$rows): ?>
                            <tr>
                                <td colspan="15" class="text-center text-muted py-5">
                                    No reservations found.
                                </td>
                            </tr>
                        <?php else: ?>
                            <?php foreach ($rows as $row): ?>
                                <?php
                                $effectiveStatus = reservation_frontdesk_status_value($row);
                                [$statusLabel, $statusClass] = status_badge_meta($effectiveStatus);
                                $isCancelled = ((string)$row['status'] === '0');
                                $isNoShow = ($effectiveStatus === 'no_show');
                                $statusNote = reservation_frontdesk_status_note($row);

                                /*
                                   Payment status is no longer manually editable.
                                   It is calculated from actual recorded payments:
                                   0 = Unpaid, 1 = Part Paid, 2 = Paid.
                                */
                                $recordedPayments = (float)($row['total_payments'] ?? 0);
                                $voidedPayments = (float)($row['voided_payments'] ?? 0);
                                $paymentComputedStatus = 0;
                                if ($recordedPayments > 0.00001) {
                                    $paymentComputedStatus = ($recordedPayments + 0.00001 >= (float)($row['grand_total'] ?? 0)) ? 2 : 1;
                                }
                                [$paymentLabel, $paymentClass] = payment_badge_meta($paymentComputedStatus);

                                $guestName = trim(($row['firstname'] ?? '') . ' ' . ($row['lastname'] ?? ''));
                                if ($guestName === '') {
                                    $guestName = 'Walk-in / Unlinked';
                                }

                                $agentName = trim((string)($row['agent_name'] ?? ''));
                                if ($agentName === '') {
                                    $agentName = 'Direct';
                                }

                                $contactName = trim((string)($row['contact_name'] ?? ''));
                                if ($contactName === '') {
                                    $contactName = '-';
                                }

                                $createdByName = trim((string)($row['created_by_name'] ?? ''));
                                if ($createdByName === '') {
                                    $createdByName = $contactName !== '-' ? $contactName : 'System / Unknown';
                                }

                                $updatedByName = trim((string)($row['updated_by_name'] ?? ''));
                                if ($updatedByName === '') {
                                    $updatedByName = '-';
                                }

                                $originatingAgent = trim((string)($row['originating_agent'] ?? ''));
                                if ($originatingAgent === '') {
                                    $originatingAgent = '-';
                                }

                                $voucherNo = trim((string)($row['voucher_no'] ?? ''));
                                if ($voucherNo === '') {
                                    $voucherNo = '-';
                                }

                                $nationalityName = trim((string)($row['nationality_name'] ?? ''));
                                if ($nationalityName === '') {
                                    $nationalityName = '-';
                                }

                                $propertyLabel = trim((string)($row['property_name'] ?? ''));
                                if ($propertyLabel === '') {
                                    $propertyLabel = 'Not set';
                                }
                                if (!empty($row['property_code'])) {
                                    $propertyLabel .= ' (' . $row['property_code'] . ')';
                                }

                                $nights = (int)($row['nights'] ?? 0);
                                if ($nights <= 0 && !empty($row['check_in']) && !empty($row['check_out'])) {
                                    $inTs = strtotime((string)$row['check_in']);
                                    $outTs = strtotime((string)$row['check_out']);
                                    if ($inTs && $outTs && $outTs > $inTs) {
                                        $nights = (int)(($outTs - $inTs) / 86400);
                                    }
                                }

                                $baseTotal = (float)($row['final_room_total'] ?? $row['totalamount'] ?? 0);
                                $calculatedRoomTotal = (float)($row['calculated_room_total'] ?? $baseTotal);
                                $overrideNights = (int)($row['override_nights'] ?? 0);
                                $overrideTypes = trim((string)($row['override_types'] ?? ''));
                                $hasRateOverride = $overrideNights > 0;
                                $extrasTotal = (float)($row['extras_total'] ?? 0);
                                $grandTotal = (float)($row['grand_total'] ?? ($baseTotal + $extrasTotal));
                                $cancellationOriginalBalance = (float)($row['cancellation_original_balance'] ?? 0);
                                $cancellationAdjustment = (float)($row['cancellation_adjustment_amount'] ?? 0);
                                $cancellationPolicyCharge = (float)($row['cancellation_policy_charge'] ?? 0);
                                $cancellationFinalBalance = (float)($row['cancellation_final_balance'] ?? 0);
                                $cancellationPolicyAction = trim((string)($row['cancellation_policy_action'] ?? ''));
                                $noShowOriginalBalance = (float)($row['no_show_original_balance'] ?? 0);
                                $noShowAdjustment = (float)($row['no_show_adjustment_amount'] ?? 0);
                                $noShowPolicyCharge = (float)($row['no_show_policy_charge'] ?? 0);
                                $noShowFinalBalance = (float)($row['no_show_final_balance'] ?? 0);
                                $noShowPolicyAction = trim((string)($row['no_show_policy_action'] ?? ''));
                                if ($isCancelled && abs($cancellationOriginalBalance) <= 0.00001) {
                                    $cancellationOriginalBalance = $calculatedRoomTotal;
                                }
                                if ($isNoShow && abs($noShowOriginalBalance) <= 0.00001) {
                                    $noShowOriginalBalance = $calculatedRoomTotal;
                                }

                                $roomMix = trim((string)($row['room_mix'] ?? ''));
                                if ($roomMix === '') {
                                    $roomMix = 'Not set';
                                }

                                $expectedArrivalTime = trim((string)($row['expected_arrival_time'] ?? ''));
                                $expectedArrivalLabel = '-';
                                if ($expectedArrivalTime !== '') {
                                    $etaTs = strtotime($expectedArrivalTime);
                                    $expectedArrivalLabel = $etaTs ? date('H:i', $etaTs) . ' hrs' : substr($expectedArrivalTime, 0, 5) . ' hrs';
                                }

                                $extraBedsRequired = (int)($row['extra_beds_required'] ?? 0);
                                $cotsRequired = (int)($row['cots_required'] ?? 0);
                                $wheelchairRequired = (int)($row['wheelchair_room_required'] ?? 0);
                                $interleadingRequired = (int)($row['interleading_required'] ?? 0);
                                $roomSetupNotes = trim((string)($row['room_setup_notes'] ?? ''));
                                $housekeepingNotes = trim((string)($row['housekeeping_notes'] ?? ''));
                                $specialOccasion = trim((string)($row['special_occasion'] ?? ''));
                                $roomRequirementParts = [];
                                if ($extraBedsRequired > 0) $roomRequirementParts[] = $extraBedsRequired . ' extra bed' . ($extraBedsRequired === 1 ? '' : 's');
                                if ($cotsRequired > 0) $roomRequirementParts[] = $cotsRequired . ' cot' . ($cotsRequired === 1 ? '' : 's');
                                if ($wheelchairRequired > 0) $roomRequirementParts[] = 'Wheelchair room';
                                if ($interleadingRequired > 0) $roomRequirementParts[] = 'Interleading';
                                if ($specialOccasion !== '') $roomRequirementParts[] = 'Occasion: ' . $specialOccasion;
                                if ($roomSetupNotes !== '') $roomRequirementParts[] = 'Setup: ' . $roomSetupNotes;
                                if ($housekeepingNotes !== '') $roomRequirementParts[] = 'Housekeeping: ' . $housekeepingNotes;
                                $roomRequirementsLabel = $roomRequirementParts ? implode(' • ', $roomRequirementParts) : 'None';

                                $waitlistUrl = '/rooms/modules/reservations/waitlist.php?' . http_build_query([
                                    'property_id' => (int)($row['property_id'] ?? 0),
                                    'check_in' => (string)($row['check_in'] ?? ''),
                                    'check_out' => (string)($row['check_out'] ?? ''),
                                    'adults' => (int)($row['adults'] ?? 0),
                                    'kids' => (int)($row['kids'] ?? 0),
                                    'totalrooms' => (int)($row['totalrooms'] ?? 1),
                                    'order_id' => (int)$row['id'],
                                ]);
                                ?>
                                <tr class="<?php echo $isNoShow ? 'row-status-noshow row-no-show' : ('row-status-' . (is_numeric($effectiveStatus) ? (int)$effectiveStatus : 0)); ?> <?php echo $isCancelled ? 'row-cancelled' : ''; ?> js-preview-row"
                                    data-is-cancelled="<?php echo $isCancelled ? '1' : '0'; ?>"
                                    data-is-no-show="<?php echo $isNoShow ? '1' : '0'; ?>"
                                    data-order-id="<?php echo (int)$row['id']; ?>"
                                    data-order-no="<?php echo h($row['order_no'] ?: ('#' . $row['id'])); ?>"
                                    data-guest="<?php echo h($guestName); ?>"
                                    data-property="<?php echo h($propertyLabel); ?>"
                                    data-agent="<?php echo h($agentName); ?>"
                                    data-created-by="<?php echo h($createdByName); ?>"
                                    data-updated-by="<?php echo h($updatedByName); ?>"
                                    data-updated-on="<?php echo h(!empty($row['updated_on']) ? display_date_local($row['updated_on'], 'd M Y H:i') : ''); ?>"
                                    data-nationality="<?php echo h($nationalityName); ?>"
                                    data-stay="<?php echo h(display_date_local($row['check_in']) . ' to ' . display_date_local($row['check_out'])); ?>"
                                    data-nights="<?php echo (int)$nights; ?>"
                                    data-room-mix="<?php echo h($roomMix); ?>"
                                    data-rooms="<?php echo (int)($row['totalrooms'] ?? 1); ?>"
                                    data-pax="<?php echo (int)($row['adults'] ?? 0); ?>A / <?php echo (int)($row['kids'] ?? 0); ?>C"
                                    data-expected-arrival="<?php echo h($expectedArrivalLabel); ?>"
                                    data-room-requirements="<?php echo h($roomRequirementsLabel); ?>"
                                    data-total="<?php echo h(display_money_local($grandTotal)); ?>"
                                    data-base-total="<?php echo h(display_money_local($baseTotal)); ?>"
                                    data-extras-total="<?php echo h(display_money_local($extrasTotal)); ?>"
                                    data-rate-override="<?php echo $hasRateOverride ? '1' : '0'; ?>"
                                    data-rate-override-text="<?php echo h($hasRateOverride ? ($overrideNights . ' night(s)' . ($overrideTypes !== '' ? ' • ' . $overrideTypes : '')) : ''); ?>"
                                    data-status="<?php echo h($statusLabel); ?>"
                                    data-payment="<?php echo h($paymentLabel); ?>"
                                    data-paid="<?php echo h(display_money_local($recordedPayments)); ?>"
                                    data-voided-paid="<?php echo h(display_money_local($voidedPayments)); ?>">
                                    <td>
                                        <div class="data-stack">
                                            <strong><?php echo h($row['order_no'] ?: ('#' . $row['id'])); ?></strong>
                                            <small>ID <?php echo (int)$row['id']; ?></small>
                                            <?php if ($isCancelled): ?>
                                                <span class="cancelled-ribbon"><i class="bi bi-x-octagon"></i> CANCELLED</span>
                                            <?php elseif ($isNoShow): ?>
                                                <span class="cancelled-ribbon no-show-ribbon"><i class="bi bi-person-x"></i> NO SHOW</span>
                                            <?php endif; ?>
                                        </div>
                                    </td>

                                    <td>
                                        <div class="data-stack">
                                            <strong><?php echo h($propertyLabel); ?></strong>
                                        </div>
                                    </td>

                                    <td>
                                        <div class="data-stack">
                                            <strong><?php echo h($guestName); ?></strong>
                                            <small><?php echo h(!empty($row['ordered_on']) ? display_date_local($row['ordered_on'], 'd M Y H:i') : ''); ?></small>
                                        </div>
                                    </td>

                                    <td>
                                        <div class="data-stack">
                                            <strong><?php echo h($createdByName); ?></strong>
                                            <small>Created <?php echo h(!empty($row['ordered_on']) ? display_date_local($row['ordered_on'], 'd M Y H:i') : '-'); ?></small>
                                            <?php if ($updatedByName !== '-' && !empty($row['updated_on'])): ?>
                                                <small>Updated by <?php echo h($updatedByName); ?> • <?php echo h(display_date_local($row['updated_on'], 'd M Y H:i')); ?></small>
                                            <?php endif; ?>
                                        </div>
                                    </td>

                                    <td><?php echo h($agentName); ?></td>
                                    <td><?php echo h($nationalityName); ?></td>

                                    <td>
                                        <div class="data-stack">
                                            <strong><?php echo h(display_date_local($row['check_in'])); ?></strong>
                                            <small>to <?php echo h(display_date_local($row['check_out'])); ?></small>
                                            <?php if ($expectedArrivalLabel !== '-'): ?>
                                                <small><i class="bi bi-clock me-1"></i>ETA <?php echo h($expectedArrivalLabel); ?></small>
                                            <?php endif; ?>
                                        </div>
                                    </td>

                                    <td><?php echo (int)$nights; ?></td>

                                    <td>
                                        <?php if ($roomMix !== 'Not set'): ?>
                                            <div class="data-stack">
                                                <strong><?php echo h($roomMix); ?></strong>
                                                <?php if (stripos($roomMix, 'Triple / Extra Bed') !== false): ?>
                                                    <span class="triple-room-note"><i class="bi bi-person-plus"></i> Triple room included</span>
                                                <?php endif; ?>
                                                <?php if ($roomRequirementParts): ?>
                                                    <span class="room-setup-note" title="<?php echo h($roomRequirementsLabel); ?>"><i class="bi bi-list-check"></i> <span class="room-setup-note-text"><?php echo h($roomRequirementsLabel); ?></span></span>
                                                <?php endif; ?>
                                            </div>
                                        <?php else: ?>
                                            <span class="text-muted">Not set</span>
                                        <?php endif; ?>
                                    </td>

                                    <td>
                                        <span class="badge text-bg-light border text-dark">
                                            <?php echo (int)($row['totalrooms'] ?? 1); ?> room(s)
                                        </span>
                                    </td>

                                    <td>
                                        <span class="booking-ref">
                                            <?php echo (int)($row['adults'] ?? 0); ?>A / <?php echo (int)($row['kids'] ?? 0); ?>C
                                        </span>
                                    </td>

                                    <td class="text-end">
                                        <div class="amount-main">
                                            <?php echo display_money_local($grandTotal); ?>
                                        </div>
                                        <?php if ($isCancelled): ?>
                                            <div class="amount-sub text-danger">
                                                Original <?php echo display_money_local($cancellationOriginalBalance); ?>
                                                <?php if ($cancellationAdjustment > 0): ?> • Waived <?php echo display_money_local($cancellationAdjustment); ?><?php endif; ?>
                                                <?php if ($cancellationPolicyCharge > 0): ?> • Policy Charge <?php echo display_money_local($cancellationPolicyCharge); ?><?php endif; ?>
                                            </div>
                                        <?php elseif ($isNoShow): ?>
                                            <div class="amount-sub text-danger">
                                                Original <?php echo display_money_local($noShowOriginalBalance); ?>
                                                <?php if ($noShowAdjustment > 0): ?> • Waived <?php echo display_money_local($noShowAdjustment); ?><?php endif; ?>
                                                <?php if ($noShowPolicyCharge > 0): ?> • Policy Charge <?php echo display_money_local($noShowPolicyCharge); ?><?php endif; ?>
                                            </div>
                                        <?php elseif ($extrasTotal > 0): ?>
                                            <div class="amount-sub">
                                                Base <?php echo display_money_local($baseTotal); ?> + Extras <?php echo display_money_local($extrasTotal); ?>
                                            </div>
                                        <?php endif; ?>

                                        <?php if ($hasRateOverride): ?>
                                            <div class="rate-override-note">
                                                <i class="bi bi-pencil-square"></i>
                                                Rate override: <?php echo (int)$overrideNights; ?> night(s)
                                            </div>
                                            <?php if (abs($calculatedRoomTotal - $baseTotal) > 0.00001): ?>
                                                <div class="amount-sub">
                                                    Calculated <?php echo display_money_local($calculatedRoomTotal); ?> → Final <?php echo display_money_local($baseTotal); ?>
                                                </div>
                                            <?php endif; ?>
                                        <?php endif; ?>
                                    </td>

                                    <td>
                                        <div class="data-stack">
                                            <span class="badge text-bg-<?php echo h($paymentClass); ?>">
                                                <?php echo h($paymentLabel); ?>
                                            </span>
                                            <small>Paid <?php echo display_money_local($recordedPayments); ?></small>
                                            <?php if ($voidedPayments > 0): ?>
                                                <small class="text-danger">Voided <?php echo display_money_local($voidedPayments); ?></small>
                                            <?php endif; ?>
                                        </div>
                                    </td>

                                    <td>
                                        <?php if ($isCancelled): ?>
                                            <span class="badge text-bg-danger">
                                                <i class="bi bi-x-octagon me-1"></i>Cancelled
                                            </span>
                                            <span class="cancelled-lock-note">
                                                Locked<?php echo !empty($row['cancellation_inventory_released']) ? ' / released' : ' / inventory held'; ?>
                                                <?php if ($cancellationPolicyAction !== ''): ?> • <?php echo h(ucwords(str_replace('_',' ', $cancellationPolicyAction))); ?><?php endif; ?>
                                            </span>
                                            <?php if ($statusNote !== ''): ?>
                                                <div class="small text-muted mt-1"><?php echo h($statusNote); ?></div>
                                            <?php endif; ?>
                                        <?php elseif ($isNoShow): ?>
                                            <span class="badge text-bg-danger">
                                                <i class="bi bi-person-x me-1"></i>No Show
                                            </span>
                                            <span class="cancelled-lock-note">
                                                Locked<?php echo !empty($row['no_show_inventory_released']) ? ' / released' : ' / inventory held'; ?>
                                                <?php if ($noShowPolicyAction !== ''): ?> • <?php echo h(ucwords(str_replace('_',' ', $noShowPolicyAction))); ?><?php endif; ?>
                                            </span>
                                            <?php if ($statusNote !== ''): ?>
                                                <div class="small text-muted mt-1"><?php echo h($statusNote); ?></div>
                                            <?php endif; ?>
                                        <?php else: ?>
                                            <select class="form-select quick-select js-inline-update"
                                                    data-order-id="<?php echo (int)$row['id']; ?>"
                                                    data-field="status">
                                                <option value="1" <?php echo (string)$effectiveStatus === '1' ? 'selected' : ''; ?>>Confirmed</option>
                                                <option value="2" <?php echo (string)$effectiveStatus === '2' ? 'selected' : ''; ?>>Provisional</option>
                                                <option value="3" <?php echo (string)$effectiveStatus === '3' ? 'selected' : ''; ?>>In House</option>
                                                <option value="4" <?php echo (string)$effectiveStatus === '4' ? 'selected' : ''; ?>>Checked Out</option>
                                            </select>
                                            <div class="small text-muted mt-1">
                                                <?php echo $statusNote !== '' ? h($statusNote) . ' • ' : ''; ?>Use Actions → Cancel Booking for cancellation policy.
                                            </div>
                                        <?php endif; ?>
                                    </td>

                                    <td class="text-end">
                                        <div class="dropdown dropstart action-dropdown">
                                            <button class="btn btn-sm btn-primary dropdown-toggle airbnb-action action-menu-btn"
        type="button"
        aria-expanded="false">
    Actions
</button>

                                            <ul class="dropdown-menu dropdown-menu-end">
                                                <?php if ($isCancelled || $isNoShow): ?>
                                                    <li>
                                                        <span class="dropdown-item text-danger fw-bold">
                                                            <i class="<?php echo $isNoShow ? 'bi bi-person-x' : 'bi bi-x-octagon'; ?> me-2"></i><?php echo $isNoShow ? 'No Show Booking' : 'Cancelled Booking'; ?>
                                                        </span>
                                                    </li>
                                                    <li>
                                                        <span class="dropdown-item small text-muted">
                                                            <i class="bi bi-lock me-2"></i>Locked: editing, extras and payments disabled
                                                        </span>
                                                    </li>
                                                    <li><hr class="dropdown-divider"></li>
                                                    <?php if ($isCancelled): ?>
                                                    <li>
                                                        <button type="button" class="dropdown-item text-success fw-semibold btn-reinstate-booking"
                                                                data-order-id="<?php echo (int)$row['id']; ?>"
                                                                data-order-no="<?php echo h($row['order_no'] ?: ('#' . $row['id'])); ?>"
                                                                data-guest-name="<?php echo h($guestName); ?>">
                                                            <i class="bi bi-arrow-counterclockwise me-2"></i>Reinstate Booking
                                                        </button>
                                                    </li>
                                                    <?php endif; ?>
                                                    <li>
                                                        <a class="dropdown-item" href="#"
                                                           onclick="event.preventDefault(); event.stopPropagation(); openApplyRate(<?php echo (int)$row['id']; ?>, { onDone: function(){ location.reload(); } });">
                                                            <i class="bi bi-cash-coin me-2 text-primary"></i>Apply Rate
                                                        </a>
                                                    </li>
                                                    <li>
                                                        <a class="dropdown-item" href="/rooms/modules/payments/folio_view.php?order_id=<?php echo (int)$row['id']; ?>">
                                                            <i class="bi bi-journal-richtext me-2 text-secondary"></i>View Final Folio
                                                        </a>
                                                    </li>

                                                    <li>
                                                        <a class="dropdown-item" href="/rooms/modules/communications/index.php?order_id=<?php echo (int)$row['id']; ?>">
                                                            <i class="bi bi-chat-square-text me-2 text-info"></i>Communications
                                                        </a>
                                                    </li>

                                                    <li class="dropdown-submenu proforma-submenu">
                                                        <a class="dropdown-item d-flex align-items-center justify-content-between" href="#" onclick="event.preventDefault(); event.stopPropagation();">
                                                            <span><i class="bi bi-file-earmark-pdf me-2 text-danger"></i>Final Documents</span>
                                                            <i class="bi bi-chevron-right small text-muted"></i>
                                                        </a>
                                                        <ul class="dropdown-menu proforma-submenu-menu">
                                                            <li>
                                                                <a class="dropdown-item" href="/rooms/modules/payments/proforma_master_folio.php?order_id=<?php echo (int)$row['id']; ?>" target="_blank">
                                                                    <i class="bi bi-file-earmark-text me-2 text-danger"></i>Final Proforma
                                                                </a>
                                                            </li>
                                                            <li>
                                                                <a class="dropdown-item" href="/rooms/modules/payments/proforma_master_folio.php?order_id=<?php echo (int)$row['id']; ?>" target="_blank">
                                                                    <i class="bi bi-collection me-2 text-dark"></i>Main Folio Proforma
                                                                </a>
                                                            </li>
                                                            <li><hr class="dropdown-divider my-1"></li>
                                                            <li><h6 class="dropdown-header py-1"><i class="bi bi-envelope me-1"></i>Email Proforma</h6></li>
                                                            <li>
                                                                <a class="dropdown-item" href="/rooms/modules/payments/email_proforma.php?order_id=<?php echo (int)$row['id']; ?>&type=standard" target="_blank">
                                                                    <i class="bi bi-envelope-paper me-2 text-danger"></i>Email Final Proforma
                                                                </a>
                                                            </li>
                                                            <li>
                                                                <a class="dropdown-item" href="/rooms/modules/payments/email_proforma.php?order_id=<?php echo (int)$row['id']; ?>&type=master" target="_blank">
                                                                    <i class="bi bi-envelope-paper me-2 text-dark"></i>Email Main Folio Proforma
                                                                </a>
                                                            </li>
                                                        </ul>
                                                    </li>
                                                <?php else: ?>
                                                    <li>
                                                        <a class="dropdown-item" href="#">
                                                            <i class="bi bi-pencil-square me-2 text-primary"></i>Edit Booking
                                                        </a>
                                                        <a class="dropdown-item" href="#">
                                                            <i class="bi bi-cash-coin me-2 text-success"></i>Edit Nightly Pricing
                                                        </a>
                                                        <button type="button" class="dropdown-item btn-edit-pricing"
                                                                data-order-id="<?php echo (int)$row['id']; ?>"
                                                                data-order-no="<?php echo h($row['order_no'] ?: ('#' . $row['id'])); ?>">
                                                            <i class="bi bi-table me-2 text-info"></i>Edit Pricing (spreadsheet)
                                                        </button>
                                                    </li>

                                                    <li>
                                                        <a class="dropdown-item" href="/rooms/modules/payments/folio_view.php?order_id=<?php echo (int)$row['id']; ?>">
                                                            <i class="bi bi-journal-richtext me-2 text-secondary"></i>Folio
                                                        </a>
                                                    </li>


                                                    <li>
                                                        <a class="dropdown-item" href="/rooms/modules/communications/index.php?order_id=<?php echo (int)$row['id']; ?>">
                                                            <i class="bi bi-chat-square-text me-2 text-info"></i>Communications
                                                        </a>
                                                    </li>

                                                    <li class="dropdown-submenu proforma-submenu">
                                                        <a class="dropdown-item d-flex align-items-center justify-content-between" href="#" onclick="event.preventDefault(); event.stopPropagation();">
                                                            <span><i class="bi bi-file-earmark-pdf me-2 text-danger"></i>Proformas</span>
                                                            <i class="bi bi-chevron-right small text-muted"></i>
                                                        </a>
                                                        <ul class="dropdown-menu proforma-submenu-menu">
                                                            <li>
                                                                <a class="dropdown-item" href="/rooms/modules/payments/proforma_invoice.php?order_id=<?php echo (int)$row['id']; ?>" target="_blank">
                                                                    <i class="bi bi-file-earmark-text me-2 text-danger"></i>Full Proforma
                                                                </a>
                                                            </li>
                                                            <li>
                                                                <a class="dropdown-item" href="/rooms/modules/payments/proforma_master_folio.php?order_id=<?php echo (int)$row['id']; ?>" target="_blank">
                                                                    <i class="bi bi-collection me-2 text-dark"></i>Main Folio Proforma
                                                                </a>
                                                            </li>
                                                            <li>
                                                                <a class="dropdown-item" href="/rooms/modules/payments/proforma_room_account.php?order_id=<?php echo (int)$row['id']; ?>" target="_blank">
                                                                    <i class="bi bi-door-open me-2 text-primary"></i>Room Proforma
                                                                </a>
                                                            </li>
                                                            <li>
                                                                <a class="dropdown-item" href="/rooms/modules/payments/guest_folios.php?order_id=<?php echo (int)$row['id']; ?>">
                                                                    <i class="bi bi-person-badge me-2 text-success"></i>Guest Folio Proforma
                                                                </a>
                                                            </li>
                                                            <li><hr class="dropdown-divider my-1"></li>
                                                            <li><h6 class="dropdown-header py-1"><i class="bi bi-envelope me-1"></i>Email Proforma</h6></li>
                                                            <li>
                                                                <a class="dropdown-item" href="/rooms/modules/payments/email_proforma.php?order_id=<?php echo (int)$row['id']; ?>&type=standard" target="_blank">
                                                                    <i class="bi bi-envelope-paper me-2 text-danger"></i>Email Full Proforma
                                                                </a>
                                                            </li>
                                                            <li>
                                                                <a class="dropdown-item" href="/rooms/modules/payments/email_proforma.php?order_id=<?php echo (int)$row['id']; ?>&type=master" target="_blank">
                                                                    <i class="bi bi-envelope-paper me-2 text-dark"></i>Email Main Folio Proforma
                                                                </a>
                                                            </li>
                                                            <li>
                                                                <a class="dropdown-item" href="/rooms/modules/payments/email_proforma.php?order_id=<?php echo (int)$row['id']; ?>&type=room" target="_blank">
                                                                    <i class="bi bi-envelope-paper me-2 text-primary"></i>Email Room Proforma
                                                                </a>
                                                            </li>
                                                        </ul>
                                                    </li>

                                                    <li>
                                                        <a class="dropdown-item" href="/rooms/modules/payments/payments.php?order_id=<?php echo (int)$row['id']; ?>">
                                                            <i class="bi bi-credit-card-2-front me-2 text-warning"></i>Payments
                                                        </a>
                                                    </li>

                                                    <li>
                                                        <a class="dropdown-item" href="/rooms/modules/payments/extras.php?order_id=<?php echo (int)$row['id']; ?>">
                                                            <i class="bi bi-plus-circle me-2 text-success"></i>Extras
                                                        </a>
                                                    </li>

                                                    <li><hr class="dropdown-divider"></li>

                                                    <li>
                                                        <a class="dropdown-item" href="<?php echo h($waitlistUrl); ?>">
                                                            <i class="bi bi-hourglass-split me-2 text-danger"></i>Waitlist
                                                        </a>
                                                    </li>

                                                    <li>
                                                        <button type="button"
                                                                class="dropdown-item text-danger btn-cancel-booking"
                                                                data-order-id="<?php echo (int)$row['id']; ?>"
                                                                data-order-no="<?php echo h($row['order_no'] ?: ('#' . $row['id'])); ?>"
                                                                data-guest-name="<?php echo h($guestName); ?>"
                                                                data-grand-total="<?php echo h(number_format((float)$grandTotal, 2, '.', '')); ?>">
                                                            <i class="bi bi-x-circle me-2"></i>Cancel Booking
                                                        </button>
                                                    </li>
                                                <?php endif; ?>
                                            </ul>
                                        </div>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                        <?php endif; ?>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</div>

<?php if ($waitlistAlerts): ?>
<div class="modal fade" id="waitlistAlertModal" tabindex="-1" aria-labelledby="waitlistAlertModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-lg modal-dialog-scrollable">
        <div class="modal-content">
            <div class="modal-header">
                <div>
                    <h5 class="modal-title mb-1" id="waitlistAlertModalLabel">Rooms Now Available</h5>
                    <div class="small text-muted">These waitlisted requests now have room availability and can be confirmed.</div>
                </div>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>

            <div class="modal-body">
                <div class="list-group list-group-flush">
                    <?php foreach ($waitlistAlerts as $alert): ?>
                        <?php
                        $propertyText = trim((string)($alert['property_name'] ?? ''));
                        if (!empty($alert['property_code'])) {
                            $propertyText .= ' (' . $alert['property_code'] . ')';
                        }
                        ?>
                        <div class="list-group-item px-0">
                            <div class="d-flex flex-wrap justify-content-between align-items-start gap-3">
                                <div>
                                    <div class="fw-bold"><?php echo h($alert['guest_name'] ?: ('Waitlist #' . $alert['id'])); ?></div>
                                    <div class="text-muted small">
                                        <?php echo h($alert['waitlist_no'] ?: ('WL-' . $alert['id'])); ?>
                                        <?php if ($propertyText !== ''): ?>
                                            • <?php echo h($propertyText); ?>
                                        <?php endif; ?>
                                        <?php if (!empty($alert['room_type'])): ?>
                                            • <?php echo h($alert['room_type']); ?>
                                        <?php endif; ?>
                                    </div>
                                    <div class="small mt-1">
                                        Stay: <strong><?php echo h(display_date_local($alert['check_in'])); ?></strong>
                                        to
                                        <strong><?php echo h(display_date_local($alert['check_out'])); ?></strong>
                                        • Rooms: <strong><?php echo (int)($alert['totalrooms'] ?? 1); ?></strong>
                                        • Pax: <strong><?php echo (int)($alert['adults'] ?? 0); ?>A / <?php echo (int)($alert['kids'] ?? 0); ?>C</strong>
                                    </div>
                                    <div class="small text-success mt-1">
                                        Available rooms found: <?php echo (int)($alert['available_rooms_last'] ?? 0); ?>
                                        <?php if (!empty($alert['availability_found_at'])): ?>
                                            • <?php echo h(display_date_local($alert['availability_found_at'], 'd M Y H:i')); ?>
                                        <?php endif; ?>
                                    </div>
                                </div>

                                <div class="d-flex gap-2 flex-wrap">
                                    <a href="/rooms/modules/reservations/reservation_form.php?waitlist_id=<?php echo (int)$alert['id']; ?>"
                                       class="btn btn-sm btn-success">
                                        <i class="bi bi-check2-circle me-1"></i>Confirm Booking
                                    </a>
                                    <a href="/rooms/modules/reservations/waitlist.php"
                                       class="btn btn-sm btn-outline-danger">
                                        Open Waitlist
                                    </a>
                                </div>
                            </div>
                        </div>
                    <?php endforeach; ?>
                </div>
            </div>

            <div class="modal-footer">
                <a href="/rooms/modules/reservations/waitlist.php" class="btn btn-outline-danger">
                    Open Waitlist
                </a>
                <button type="button" class="btn btn-light" data-bs-dismiss="modal">Close</button>
            </div>
        </div>
    </div>
</div>
<?php endif; ?>

<?php if (isset($_GET['cancelled'])): ?>
    <div class="alert alert-success mt-4">Reservation cancelled successfully.</div>
<?php endif; ?>

<?php if (isset($_GET['reinstated'])): ?>
    <div class="alert alert-success mt-4"><i class="bi bi-arrow-counterclockwise me-1"></i>Booking reinstated successfully. Rooms have been re-held.</div>
<?php endif; ?>

<?php if (isset($_GET['cancel_error'])): ?>
    <div class="alert alert-danger mt-4">
        Cancellation failed.
        <?php if (!empty($_GET['msg'])): ?>
            <div class="small mt-1"><?php echo h((string)$_GET['msg']); ?></div>
        <?php endif; ?>
    </div>
<?php endif; ?>

<div class="preview-backdrop" id="reservationPreviewBackdrop"></div>

<aside class="preview-panel" id="reservationPreviewPanel" aria-hidden="true">
    <div class="preview-hero">
        <div class="d-flex justify-content-between align-items-start gap-3">
            <div>
                <div class="small text-white-50 mb-1">Reservation Preview</div>
                <h4 class="mb-1" id="previewOrderNo">-</h4>
                <div id="previewGuest" class="text-white-50">-</div>
            </div>
            <button type="button" class="btn btn-sm btn-light" id="previewCloseBtn">
                <i class="bi bi-x-lg"></i>
            </button>
        </div>
    </div>

    <div class="preview-body">
        <div class="d-flex flex-wrap gap-2 mb-3">
            <span class="preview-pill"><i class="bi bi-house"></i> <span id="previewProperty">-</span></span>
            <span class="preview-pill"><i class="bi bi-calendar-event"></i> <span id="previewStay">-</span></span>
            <span class="preview-pill"><i class="bi bi-people"></i> <span id="previewPax">-</span></span>
        </div>

        <div class="row g-3 mb-3">
            <div class="col-6">
                <div class="preview-stat">
                    <small>Nights</small>
                    <strong id="previewNights">0</strong>
                </div>
            </div>
            <div class="col-6">
                <div class="preview-stat">
                    <small>Rooms</small>
                    <strong id="previewRooms">0</strong>
                </div>
            </div>
            <div class="col-12">
                <div class="preview-stat">
                    <small>Total Value</small>
                    <strong id="previewTotal">0.00</strong>
                    <div id="previewRateOverrideNote" class="rate-override-note d-none">
                        <i class="bi bi-pencil-square"></i>
                        <span id="previewRateOverrideText">Rate override</span>
                    </div>
                </div>
            </div>
        </div>

        <div class="preview-stat mb-3">
            <small>Room Mix</small>
            <strong id="previewRoomMix">-</strong>
            <div id="previewTripleNote" class="triple-room-note d-none"><i class="bi bi-person-plus"></i> Triple room included</div>
        </div>

        <div class="preview-stat mb-3">
            <small>Expected Arrival</small>
            <strong id="previewExpectedArrival">-</strong>
        </div>

        <div class="preview-stat mb-3">
            <small>Room Setup / Housekeeping</small>
            <strong id="previewRoomRequirements">None</strong>
        </div>

        <div class="preview-stat mb-3">
            <small>Agent</small>
            <strong id="previewAgent">-</strong>
        </div>

        <div class="preview-stat mb-3">
            <small>Created By</small>
            <strong id="previewCreatedBy">-</strong>
            <div class="amount-sub d-none" id="previewUpdatedByWrap">Last updated by <span id="previewUpdatedBy">-</span> • <span id="previewUpdatedOn">-</span></div>
        </div>

        <div class="preview-stat mb-3">
            <small>Nationality</small>
            <strong id="previewNationality">-</strong>
        </div>

        <div class="preview-stat mb-4">
            <small>Status</small>
            <strong>
                <span id="previewStatus">-</span>
                /
                <span id="previewPayment">-</span>
            </strong>
            <div class="amount-sub">Recorded payments: <span id="previewPaid">0.00</span></div>
            <div class="amount-sub text-danger d-none" id="previewVoidedPaidWrap">Voided payments: <span id="previewVoidedPaid">0.00</span></div>
        </div>

        <div class="d-grid gap-2">
            <a href="#" id="previewEditBtn" class="btn btn-dark">
                <i class="bi bi-pencil-square me-1"></i>Edit Full Booking
            </a>

            <a href="#" id="previewFolioBtn" class="btn btn-outline-secondary">
                <i class="bi bi-journal-richtext me-1"></i>Open Folio
            </a>

            <a href="#" id="previewCommunicationsBtn" class="btn btn-info text-white">
                <i class="bi bi-chat-square-text me-1"></i>Communications
            </a>

            <a href="#" id="previewPaymentsBtn" class="btn btn-outline-warning">
                <i class="bi bi-credit-card me-1"></i>Payments
            </a>

            <a href="#" id="previewExtrasBtn" class="btn btn-outline-primary">
                <i class="bi bi-plus-circle me-1"></i>Extras
            </a>
        </div>
    </div>
</aside>

<div class="modal fade" id="cancelBookingModal" tabindex="-1" aria-labelledby="cancelBookingModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-lg modal-dialog-centered">
        <form class="modal-content border-0 shadow-lg" method="post" action="/rooms/modules/reservations/reservation_list.php">
            <?php if (function_exists('csrf_token')): ?>
                <input type="hidden" name="csrf_token" value="<?php echo h(csrf_token()); ?>">
            <?php endif; ?>
            <input type="hidden" name="action" value="cancel_reservation_policy">
            <input type="hidden" name="order_id" id="cancel_order_id" value="">

            <div class="modal-header bg-danger text-white">
                <div>
                    <h5 class="modal-title" id="cancelBookingModalLabel">
                        <i class="bi bi-x-octagon me-2"></i>Cancel Reservation
                    </h5>
                    <div class="small text-white-50" id="cancel_modal_subtitle">Process cancellation according to policy.</div>
                </div>
                <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>

            <div class="modal-body">
                <div class="alert alert-warning small">
                    <strong>Clean accounting approach:</strong> the system will not delete old charges. It posts visible cancellation adjustment and policy-charge transactions, recalculates folios, locks the booking as cancelled, and releases room inventory when selected.
                </div>

                <div class="row g-3">
                    <div class="col-md-7">
                        <label class="form-label">Cancellation Policy Action</label>
                        <select name="cancel_policy_action" id="cancel_policy_action" class="form-select" required>
                            <option value="keep_balance">Charge existing folio as is</option>
                            <option value="waive_all">Waive entire current folio balance</option>
                            <option value="waive_and_charge_policy">Waive selected balance then charge cancellation fee</option>
                        </select>
                        <div class="form-text">
                            Use the third option for first-night, percentage, or custom cancellation penalties.
                        </div>
                    </div>
                    <div class="col-md-5">
                        <label class="form-label">Current Folio Balance</label>
                        <input type="text" id="cancel_current_balance_display" class="form-control fw-bold" readonly value="0.00">
                    </div>

                    <div class="col-md-6 cancel-policy-amounts d-none">
                        <label class="form-label">Cancellation Adjustment / Waiver</label>
                        <input type="number" step="0.01" min="0" name="cancel_adjustment_amount" id="cancel_adjustment_amount" class="form-control" value="0.00">
                        <div class="form-text">Credit amount to remove from the existing balance.</div>
                    </div>
                    <div class="col-md-6 cancel-policy-amounts d-none">
                        <label class="form-label">Cancellation Policy Charge</label>
                        <input type="number" step="0.01" min="0" name="cancel_policy_charge_amount" id="cancel_policy_charge_amount" class="form-control" value="0.00">
                        <div class="form-text">Debit amount to charge after waiver.</div>
                    </div>

                    <div class="col-12">
                        <label class="form-label">Cancellation Reason</label>
                        <textarea name="cancel_reason" id="cancel_reason" class="form-control" rows="3" required placeholder="Enter the cancellation reason"></textarea>
                    </div>

                    <div class="col-12">
                        <div class="form-check">
                            <input class="form-check-input" type="checkbox" name="release_inventory" value="1" id="cancel_release_inventory" checked>
                            <label class="form-check-label fw-semibold" for="cancel_release_inventory">
                                Release allocated rooms/inventory and refresh waitlist
                            </label>
                            <div class="form-text">Untick only if management wants to keep the room blocked.</div>
                        </div>
                    </div>
                </div>

                <div class="border rounded-3 p-3 bg-light mt-3">
                    <div class="d-flex justify-content-between mb-2">
                        <span class="text-muted">Booking</span>
                        <strong id="cancel_summary_order_no">-</strong>
                    </div>
                    <div class="d-flex justify-content-between mb-2">
                        <span class="text-muted">Guest</span>
                        <strong id="cancel_summary_guest">-</strong>
                    </div>
                    <div class="d-flex justify-content-between mb-2">
                        <span class="text-muted">Original / Current Balance</span>
                        <strong id="cancel_summary_total">0.00</strong>
                    </div>
                    <hr>
                    <div class="d-flex justify-content-between mb-2">
                        <span class="text-muted">Adjustment / Waiver</span>
                        <strong class="text-danger" id="cancel_preview_adjustment">0.00</strong>
                    </div>
                    <div class="d-flex justify-content-between mb-2">
                        <span class="text-muted">Policy Charge</span>
                        <strong class="text-primary" id="cancel_preview_charge">0.00</strong>
                    </div>
                    <div class="d-flex justify-content-between">
                        <span class="text-muted fw-bold">Expected Final Balance</span>
                        <strong class="text-dark" id="cancel_preview_final">0.00</strong>
                    </div>
                    <div class="small text-muted mt-2" id="cancel_fee_hint">Existing balance will remain unchanged.</div>
                </div>
            </div>

            <div class="modal-footer">
                <button type="submit" class="btn btn-danger" onclick="return confirm('Cancel this reservation and apply the selected accounting policy?');">
                    <i class="bi bi-x-circle me-1"></i>Confirm Cancellation
                </button>
                <button type="button" class="btn btn-light" data-bs-dismiss="modal">Close</button>
            </div>
        </form>
    </div>
</div>

<!-- =====================================================================
     REINSTATE BOOKING - checks availability, then restores a cancelled booking
     ===================================================================== -->
<div class="modal fade" id="reinstateBookingModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered">
        <form class="modal-content border-0 shadow-lg" method="post" action="/rooms/modules/reservations/reservation_list.php">
            <?php if (function_exists('csrf_token')): ?>
                <input type="hidden" name="csrf_token" value="<?php echo h(csrf_token()); ?>">
            <?php endif; ?>
            <input type="hidden" name="action" value="reinstate_reservation">
            <input type="hidden" name="order_id" id="reinstate_order_id" value="">

            <div class="modal-header bg-success text-white">
                <div>
                    <h5 class="modal-title"><i class="bi bi-arrow-counterclockwise me-2"></i>Reinstate Booking</h5>
                    <div class="small text-white-50" id="reinstate_subtitle">Restore this cancelled reservation.</div>
                </div>
                <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>

            <div class="modal-body">
                <div class="d-flex flex-wrap gap-3 mb-3 small text-muted">
                    <div><span class="fw-semibold text-dark" id="reinstate_guest">-</span></div>
                    <div id="reinstate_dates">-</div>
                </div>

                <div id="reinstate_loading" class="text-center text-muted py-4">
                    <div class="spinner-border spinner-border-sm me-2"></div>Checking room availability...
                </div>

                <div id="reinstate_result" class="d-none">
                    <div id="reinstate_verdict" class="alert d-none"></div>
                    <div class="table-responsive" style="max-height:40vh;overflow:auto;">
                        <table class="table table-sm align-middle mb-0">
                            <thead class="table-light"><tr><th>Room</th><th>Type</th><th class="text-end">Status</th></tr></thead>
                            <tbody id="reinstate_rooms"></tbody>
                        </table>
                    </div>
                    <div class="form-text mt-2">Reinstating restores the booking to Confirmed and re-holds the rooms.</div>
                </div>
            </div>

            <div class="modal-footer">
                <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
                <button type="submit" class="btn btn-success" id="reinstate_confirm_btn" disabled
                        onclick="return confirm('Reinstate this booking and re-hold the rooms?');">
                    <i class="bi bi-check2-circle me-1"></i>Reinstate Booking
                </button>
            </div>
        </form>
    </div>
</div>

<!-- =====================================================================
     EDIT PRICING - spreadsheet-style per-night rate editor
     ===================================================================== -->
<div class="modal fade" id="editPricingModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
        <div class="modal-content border-0 shadow-lg">
            <div class="modal-header bg-info text-white">
                <div>
                    <h5 class="modal-title"><i class="bi bi-table me-2"></i>Edit Pricing</h5>
                    <div class="small" id="epSubtitle">Manual nightly rate override</div>
                </div>
                <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                <div id="epAlert" class="alert d-none" role="alert"></div>

                <div class="d-flex flex-wrap gap-3 mb-3 small text-muted">
                    <div><span class="fw-semibold text-dark" id="epGuest">-</span></div>
                    <div>Order <span class="fw-semibold text-dark" id="epOrderNo">-</span></div>
                    <div><span id="epDates">-</span></div>
                    <div><span id="epRooms">-</span> room(s)</div>
                </div>

                <div class="alert alert-light border small py-2">
                    <i class="bi bi-info-circle me-1"></i>
                    Type a new rate into any night's <strong>Manual Rate</strong> cell to override it.
                    Leave a cell blank to keep the system rate; clear an existing override to revert that night.
                    Pricing is per night for the whole booking.
                </div>

                <div class="table-responsive" style="max-height:50vh;overflow:auto;">
                    <table class="table table-sm table-bordered align-middle mb-0" id="epTable">
                        <thead class="table-light position-sticky top-0">
                            <tr>
                                <th style="min-width:120px;">Date</th>
                                <th class="text-center" style="width:80px;">Adults</th>
                                <th class="text-center" style="width:80px;">Children</th>
                                <th class="text-end" style="min-width:110px;">System Rate</th>
                                <th class="text-end" style="min-width:140px;">Manual Rate</th>
                                <th class="text-end" style="min-width:110px;">Effective</th>
                            </tr>
                        </thead>
                        <tbody id="epBody">
                            <tr><td colspan="6" class="text-center text-muted py-4">Loading...</td></tr>
                        </tbody>
                        <tfoot>
                            <tr class="table-light fw-bold">
                                <td colspan="5" class="text-end">Booking total</td>
                                <td class="text-end" id="epTotal">0.00</td>
                            </tr>
                        </tfoot>
                    </table>
                </div>
            </div>
            <div class="modal-footer">
                <div class="me-auto small text-muted" id="epStatus"></div>
                <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
                <button type="button" class="btn btn-info text-white" id="epSaveBtn"><i class="bi bi-save me-1"></i>Save Manual Rate Entry</button>
            </div>
        </div>
    </div>
</div>

<!-- =====================================================================
     SEARCH PROGRESS OVERLAY
     Shown while the filtered search request is loading. On slow networks the
     query can take a while, so this reassures staff the search is running.
     ===================================================================== -->
<div id="searchProgressOverlay" aria-hidden="true">
    <div class="sp-box">
        <div class="sp-brand">RoomZ PMS</div>
        <div class="spinner-border sp-spinner" role="status"><span class="visually-hidden">Loading...</span></div>
        <div class="sp-title">Please wait — searching by your parameters.</div>
        <div class="sp-sub">On slow networks the search may take time...</div>
        <div class="progress sp-progress"><div class="progress-bar progress-bar-striped progress-bar-animated bg-primary" style="width:100%"></div></div>
    </div>
</div>

<style>
#searchProgressOverlay{
    position:fixed; inset:0; z-index:20000; display:none;
    background:rgba(15,23,42,.55); backdrop-filter:blur(2px);
    align-items:center; justify-content:center;
}
#searchProgressOverlay.is-visible{ display:flex; }
#searchProgressOverlay .sp-box{
    background:#fff; border-radius:16px; padding:28px 34px; text-align:center;
    box-shadow:0 20px 60px rgba(0,0,0,.35); min-width:320px; max-width:90vw;
}
#searchProgressOverlay .sp-brand{
    font-weight:800; letter-spacing:.5px; color:#0d6efd; font-size:1.1rem; margin-bottom:12px;
}
#searchProgressOverlay .sp-spinner{ width:2.4rem; height:2.4rem; color:#0d6efd; }
#searchProgressOverlay .sp-title{ font-weight:600; margin-top:14px; color:#0f172a; }
#searchProgressOverlay .sp-sub{ font-size:.85rem; color:#64748b; margin-top:4px; }
#searchProgressOverlay .sp-progress{ height:6px; margin-top:16px; border-radius:6px; }
.ep-manual-input.is-overridden{ background:#fff7ed; border-color:#fdba74; font-weight:600; }
</style>

<script>
document.addEventListener('DOMContentLoaded', function () {
    var csrfToken = '<?php echo function_exists('csrf_token') ? h(csrf_token()) : ''; ?>';

    /* ------------------------------------------------------------------ *
     *  ACTION MENU (self-managed, portalled to <body>)                    *
     *  The table scrolls inside overflow:auto which clips any dropdown.   *
     *  We manage these menus ourselves: on click, move the menu to <body>,*
     *  show it fixed next to the button, close on outside click/Esc/scroll*
     *  The click listener is in the CAPTURE phase so no ancestor handler  *
     *  calling stopPropagation() can prevent the menu from opening.       *
     * ------------------------------------------------------------------ */
    try {
    (function () {
        var open = null; // { menu, placeholder, btn }

        // Desired page-coordinate target for the menu's top-left, chosen to sit
        // next to the button (left side preferred, flipping as needed).
        function desiredTarget(menu, btn) {
            var r = btn.getBoundingClientRect();
            var mw = menu.offsetWidth, mh = menu.offsetHeight, gap = 4;
            var vw = window.innerWidth, vh = window.innerHeight;
            var sx = window.pageXOffset || document.documentElement.scrollLeft || 0;
            var sy = window.pageYOffset || document.documentElement.scrollTop || 0;

            // Viewport-relative first.
            var left = r.left - mw - gap;          // left of button
            if (left < 8) left = r.right + gap;    // else right of button
            if (left + mw > vw - 8) left = Math.max(8, vw - mw - 8);
            var top = r.top;
            if (top + mh > vh - 8) {
                top = Math.max(8, r.bottom - mh);
                if (top + mh > vh - 8) top = Math.max(8, vh - mh - 8);
            }
            // Convert to PAGE coordinates for absolute positioning on <body>.
            return { left: left + sx, top: top + sy };
        }

        function positionMenu(menu, btn) {
            menu.style.setProperty('position', 'absolute', 'important');
            menu.style.setProperty('right', 'auto', 'important');
            menu.style.setProperty('bottom', 'auto', 'important');
            menu.style.setProperty('margin', '0', 'important');
            menu.style.setProperty('transform', 'none', 'important');
            menu.style.setProperty('inset', 'auto', 'important');
            menu.style.setProperty('z-index', '200000', 'important');

            var target = desiredTarget(menu, btn);
            menu.style.setProperty('left', target.left + 'px', 'important');
            menu.style.setProperty('top', target.top + 'px', 'important');

            // Iteratively correct: measure the menu's ACTUAL page position and
            // nudge our left/top values by the error, until it lands on target.
            // This cancels out any ancestor transform/scale/offset, whatever it is.
            var sx = window.pageXOffset || document.documentElement.scrollLeft || 0;
            var sy = window.pageYOffset || document.documentElement.scrollTop || 0;
            var setLeft = target.left, setTop = target.top;
            for (var i = 0; i < 4; i++) {
                var rect = menu.getBoundingClientRect();
                var actualPageLeft = rect.left + sx;
                var actualPageTop = rect.top + sy;
                var errX = target.left - actualPageLeft;
                var errY = target.top - actualPageTop;
                if (Math.abs(errX) < 1 && Math.abs(errY) < 1) break;
                setLeft += errX;
                setTop += errY;
                menu.style.setProperty('left', setLeft + 'px', 'important');
                menu.style.setProperty('top', setTop + 'px', 'important');
            }
        }

        function closeMenu() {
            if (!open) return;
            var menu = open.menu, placeholder = open.placeholder, btn = open.btn;
            menu.classList.remove('show');
            if (placeholder && placeholder.parentNode) {
                placeholder.parentNode.insertBefore(menu, placeholder);
                placeholder.parentNode.removeChild(placeholder);
            }
            ['position','top','left','right','bottom','margin','z-index','transform','inset','display','visibility','opacity','pointer-events'].forEach(function (p) {
                menu.style.removeProperty(p);
            });
            menu.classList.remove('action-menu-portalled');
            if (btn) btn.setAttribute('aria-expanded', 'false');
            open = null;
        }

        function openMenu(btn) {
            try {
                var dd = btn.closest('.action-dropdown');
                if (!dd) return;
                var menu = dd.querySelector('.dropdown-menu');
                if (!menu) return;
                closeMenu();
                var placeholder = document.createComment('action-menu-portal');
                menu.parentNode.insertBefore(placeholder, menu);
                // Append to <body> and position absolutely in page coordinates.
                // The iterative correction in positionMenu() then cancels out any
                // transform on an ancestor, so it lands next to the button.
                document.body.appendChild(menu);
                menu.classList.add('action-menu-portalled', 'show');
                menu.style.setProperty('display', 'block', 'important');
                menu.style.setProperty('visibility', 'visible', 'important');
                menu.style.setProperty('opacity', '1', 'important');
                menu.style.setProperty('pointer-events', 'auto', 'important');
                btn.setAttribute('aria-expanded', 'true');
                open = { menu: menu, placeholder: placeholder, btn: btn };
                positionMenu(menu, btn);
            } catch (err) {
                if (window.console) console.error('Action menu open failed:', err);
            }
        }

        function handleDocClick(e) {
            var btn = e.target.closest ? e.target.closest('.action-menu-btn') : null;
            if (btn) {
                e.preventDefault();
                e.stopPropagation();
                if (open && open.btn === btn) closeMenu();
                else openMenu(btn);
                return;
            }
            if (open && open.menu.contains(e.target)) {
                var submenuToggle = e.target.closest('.dropdown-submenu > .dropdown-item');
                if (submenuToggle) return;
                setTimeout(closeMenu, 0);
                return;
            }
            closeMenu();
        }
        document.addEventListener('click', handleDocClick, true); // capture phase

        document.addEventListener('keydown', function (e) {
            if (e.key === 'Escape') closeMenu();
        });
        function reposition() { if (open) positionMenu(open.menu, open.btn); }
        window.addEventListener('resize', reposition);
        // With absolute page positioning the menu scrolls with the page. To keep
        // it aligned when the inner table scroller moves (which doesn't move the
        // menu), just close it on scroll - simplest and jitter-free.
        window.addEventListener('scroll', function () { if (open) closeMenu(); }, true);
    })();
    } catch (err) { if (window.console) console.error('Action menu init failed:', err); }

    /* ------------------------------------------------------------------ *
     *  SEARCH PROGRESS OVERLAY                                            *
     *  Shows while the filter form is loading its (possibly slow) results *
     * ------------------------------------------------------------------ */
    (function () {
        var overlay = document.getElementById('searchProgressOverlay');
        if (!overlay) return;
        var filterForm = document.querySelector('form[method="get"]');
        if (filterForm) {
            filterForm.addEventListener('submit', function () {
                overlay.classList.add('is-visible');
                overlay.setAttribute('aria-hidden', 'false');
            });
        }
        window.addEventListener('pageshow', function () {
            overlay.classList.remove('is-visible');
            overlay.setAttribute('aria-hidden', 'true');
        });
    })();

    /* ------------------------------------------------------------------ *
     *  EDIT PRICING - spreadsheet grid                                    *
     * ------------------------------------------------------------------ */
    (function () {
        var modalEl = document.getElementById('editPricingModal');
        if (!modalEl) return;
        var epModal = (typeof bootstrap !== 'undefined') ? bootstrap.Modal.getOrCreateInstance(modalEl) : null;
        var body = document.getElementById('epBody');
        var totalEl = document.getElementById('epTotal');
        var saveBtn = document.getElementById('epSaveBtn');
        var statusEl = document.getElementById('epStatus');
        var alertEl = document.getElementById('epAlert');
        var currentOrderId = 0;

        function money(n) { return (Number(n || 0)).toFixed(2); }
        function epAlert(msg, kind) {
            alertEl.className = 'alert alert-' + (kind || 'info');
            alertEl.textContent = msg;
            alertEl.classList.remove('d-none');
        }
        function epClearAlert() { alertEl.classList.add('d-none'); }

        function recomputeTotal() {
            var rows = body.querySelectorAll('tr[data-price-id]');
            var sum = 0;
            rows.forEach(function (tr) {
                var sys = parseFloat(tr.getAttribute('data-system')) || 0;
                var inp = tr.querySelector('.ep-manual-input');
                var eff = tr.querySelector('.ep-effective');
                var raw = inp ? String(inp.value).replace(/,/g, '').trim() : '';
                var effective = raw === '' ? sys : (parseFloat(raw) || 0);
                if (eff) eff.textContent = money(effective);
                if (inp) {
                    if (raw !== '' && Math.abs(effective - sys) > 0.001) inp.classList.add('is-overridden');
                    else inp.classList.remove('is-overridden');
                }
                sum += effective;
            });
            totalEl.textContent = money(sum);
        }

        function renderRows(data) {
            document.getElementById('epGuest').textContent = data.order.guest || '-';
            document.getElementById('epOrderNo').textContent = data.order.order_no || '-';
            document.getElementById('epDates').textContent = (data.order.check_in || '?') + ' \u2192 ' + (data.order.check_out || '?');
            document.getElementById('epRooms').textContent = data.order.rooms || 0;

            if (!data.rows.length) {
                body.innerHTML = '<tr><td colspan="6" class="text-center text-muted py-4">No nightly price rows for this booking.</td></tr>';
                totalEl.textContent = '0.00';
                return;
            }
            if (!data.supports_override) {
                epAlert('This installation has no rate-override columns, so manual rates are saved directly to each night total.', 'warning');
            }

            var html = '';
            data.rows.forEach(function (r) {
                var manualVal = r.overridden ? money(r.total) : '';
                html += '<tr data-price-id="' + r.id + '" data-system="' + r.system + '">'
                    + '<td class="fw-semibold">' + r.date + '</td>'
                    + '<td class="text-center">' + r.adults + '</td>'
                    + '<td class="text-center">' + r.children + '</td>'
                    + '<td class="text-end text-muted">' + money(r.system) + '</td>'
                    + '<td class="text-end"><input type="text" inputmode="decimal" class="form-control form-control-sm text-end ep-manual-input" '
                    +     'value="' + manualVal + '" placeholder="' + money(r.system) + '"></td>'
                    + '<td class="text-end ep-effective">' + money(r.total) + '</td>'
                    + '</tr>';
            });
            body.innerHTML = html;
            recomputeTotal();
        }

        function loadPricing(orderId) {
            currentOrderId = orderId;
            epClearAlert();
            body.innerHTML = '<tr><td colspan="6" class="text-center text-muted py-4">Loading...</td></tr>';
            totalEl.textContent = '0.00';
            statusEl.textContent = '';

            fetch('?ajax=pricing_load&order_id=' + encodeURIComponent(orderId), { credentials: 'same-origin' })
                .then(function (r) { return r.json(); })
                .then(function (data) {
                    if (!data || !data.ok) {
                        body.innerHTML = '<tr><td colspan="6" class="text-center text-danger py-4">' + ((data && data.message) ? data.message : 'Failed to load.') + '</td></tr>';
                        return;
                    }
                    renderRows(data);
                })
                .catch(function () {
                    body.innerHTML = '<tr><td colspan="6" class="text-center text-danger py-4">Network error loading pricing.</td></tr>';
                });
        }

        body.addEventListener('input', function (e) {
            if (e.target && e.target.classList.contains('ep-manual-input')) recomputeTotal();
        });

        saveBtn.addEventListener('click', function () {
            if (!currentOrderId) return;
            epClearAlert();
            var rows = [];
            body.querySelectorAll('tr[data-price-id]').forEach(function (tr) {
                var inp = tr.querySelector('.ep-manual-input');
                rows.push({ id: tr.getAttribute('data-price-id'), override: inp ? inp.value.trim() : '' });
            });

            saveBtn.disabled = true;
            statusEl.textContent = 'Saving...';

            var fd = new FormData();
            fd.append('action', 'pricing_save');
            fd.append('order_id', currentOrderId);
            fd.append('rows', JSON.stringify(rows));
            if (csrfToken) fd.append('csrf_token', csrfToken);

            fetch('/rooms/modules/reservations/reservation_list.php', { method: 'POST', body: fd, credentials: 'same-origin' })
                .then(function (r) { return r.json(); })
                .then(function (data) {
                    if (data && data.ok) {
                        statusEl.textContent = '';
                        epAlert((data.message || 'Saved.') + ' New booking room total: ' + money(data.room_total) + '.', 'success');
                        loadPricing(currentOrderId);
                    } else {
                        statusEl.textContent = '';
                        epAlert((data && data.message) ? data.message : 'Save failed.', 'danger');
                    }
                })
                .catch(function () {
                    statusEl.textContent = '';
                    epAlert('Network error while saving.', 'danger');
                })
                .finally(function () { saveBtn.disabled = false; });
        });

        document.querySelectorAll('.btn-edit-pricing').forEach(function (btn) {
            btn.addEventListener('click', function (e) {
                e.preventDefault();
                e.stopPropagation();
                var orderId = this.getAttribute('data-order-id') || '';
                if (!orderId) return;
                document.getElementById('epSubtitle').textContent = 'Manual nightly rate override — ' + (this.getAttribute('data-order-no') || '');
                if (epModal) epModal.show();
                loadPricing(orderId);
            });
        });
    })();

    /* ------------------------------------------------------------------ *
     *  REINSTATE BOOKING - availability check then restore                *
     * ------------------------------------------------------------------ */
    (function () {
        var modalEl = document.getElementById('reinstateBookingModal');
        if (!modalEl) return;
        var reModal = (typeof bootstrap !== 'undefined') ? bootstrap.Modal.getOrCreateInstance(modalEl) : null;
        var loadingEl = document.getElementById('reinstate_loading');
        var resultEl = document.getElementById('reinstate_result');
        var verdictEl = document.getElementById('reinstate_verdict');
        var roomsEl = document.getElementById('reinstate_rooms');
        var confirmBtn = document.getElementById('reinstate_confirm_btn');
        var orderIdInput = document.getElementById('reinstate_order_id');

        function esc(s) {
            return String(s == null ? '' : s).replace(/[&<>"']/g, function (m) {
                return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' })[m];
            });
        }

        function runCheck(orderId) {
            loadingEl.classList.remove('d-none');
            resultEl.classList.add('d-none');
            verdictEl.classList.add('d-none');
            roomsEl.innerHTML = '';
            confirmBtn.disabled = true;

            fetch('?ajax=reinstate_check&order_id=' + encodeURIComponent(orderId), { credentials: 'same-origin' })
                .then(function (r) { return r.json(); })
                .then(function (data) {
                    loadingEl.classList.add('d-none');
                    resultEl.classList.remove('d-none');

                    if (!data || !data.ok) {
                        verdictEl.className = 'alert alert-danger';
                        verdictEl.textContent = (data && data.message) ? data.message : 'Availability check failed.';
                        verdictEl.classList.remove('d-none');
                        return;
                    }

                    document.getElementById('reinstate_dates').textContent =
                        (data.check_in || '?') + ' \u2192 ' + (data.check_out || '?');

                    var html = '';
                    (data.rooms || []).forEach(function (rm) {
                        var statusHtml = rm.available
                            ? '<span class="badge text-bg-success">Available</span>'
                            : '<span class="badge text-bg-danger">Taken</span>' +
                              (rm.clash ? '<div class="small text-danger mt-1">' + esc(rm.clash.order_no) + ' ' + esc(rm.clash.guest) +
                                ' (' + esc(rm.clash.check_in) + '\u2192' + esc(rm.clash.check_out) + ')</div>' : '');
                        html += '<tr><td class="fw-semibold">' + esc(rm.room_no) + '</td><td>' + esc(rm.room_type) +
                                '</td><td class="text-end">' + statusHtml + '</td></tr>';
                    });
                    roomsEl.innerHTML = html || '<tr><td colspan="3" class="text-muted text-center py-3">No rooms on this booking.</td></tr>';

                    if (data.available) {
                        verdictEl.className = 'alert alert-success py-2';
                        verdictEl.innerHTML = '<i class="bi bi-check-circle me-1"></i>All rooms are free for these dates. You can reinstate this booking.';
                        verdictEl.classList.remove('d-none');
                        confirmBtn.disabled = false;
                    } else {
                        verdictEl.className = 'alert alert-danger py-2';
                        verdictEl.innerHTML = '<i class="bi bi-x-circle me-1"></i>One or more rooms are no longer available. Resolve the clash (e.g. reassign a room) before reinstating.';
                        verdictEl.classList.remove('d-none');
                        confirmBtn.disabled = true;
                    }
                })
                .catch(function () {
                    loadingEl.classList.add('d-none');
                    resultEl.classList.remove('d-none');
                    verdictEl.className = 'alert alert-danger';
                    verdictEl.textContent = 'Network error during availability check.';
                    verdictEl.classList.remove('d-none');
                });
        }

        document.querySelectorAll('.btn-reinstate-booking').forEach(function (btn) {
            btn.addEventListener('click', function (e) {
                e.preventDefault();
                e.stopPropagation();
                var orderId = this.getAttribute('data-order-id') || '';
                if (!orderId) return;
                orderIdInput.value = orderId;
                document.getElementById('reinstate_guest').textContent = this.getAttribute('data-guest-name') || '-';
                document.getElementById('reinstate_subtitle').textContent =
                    'Restore ' + (this.getAttribute('data-order-no') || 'booking') + '.';
                if (reModal) reModal.show();
                runCheck(orderId);
            });
        });
    })();

    <?php if ($waitlistAlerts): ?>
    var waitlistModalEl = document.getElementById('waitlistAlertModal');
    if (waitlistModalEl && typeof bootstrap !== 'undefined') {
        var waitlistModal = bootstrap.Modal.getOrCreateInstance(waitlistModalEl);
        waitlistModal.show();
    }
    <?php endif; ?>

    var panel = document.getElementById('reservationPreviewPanel');
    var backdrop = document.getElementById('reservationPreviewBackdrop');
    var closeBtn = document.getElementById('previewCloseBtn');

    function openPreview(row) {
        var orderId = row.getAttribute('data-order-id') || '';

        document.getElementById('previewOrderNo').textContent = row.getAttribute('data-order-no') || '-';
        document.getElementById('previewGuest').textContent = row.getAttribute('data-guest') || '-';
        document.getElementById('previewProperty').textContent = row.getAttribute('data-property') || '-';
        document.getElementById('previewStay').textContent = row.getAttribute('data-stay') || '-';
        document.getElementById('previewPax').textContent = row.getAttribute('data-pax') || '-';
        var previewExpectedArrivalEl = document.getElementById('previewExpectedArrival');
        if (previewExpectedArrivalEl) previewExpectedArrivalEl.textContent = row.getAttribute('data-expected-arrival') || '-';
        var previewRoomRequirementsEl = document.getElementById('previewRoomRequirements');
        if (previewRoomRequirementsEl) previewRoomRequirementsEl.textContent = row.getAttribute('data-room-requirements') || 'None';
        document.getElementById('previewNights').textContent = row.getAttribute('data-nights') || '0';
        document.getElementById('previewRooms').textContent = row.getAttribute('data-rooms') || '0';
        document.getElementById('previewTotal').textContent = row.getAttribute('data-total') || '0.00';

        var previewRateOverrideNote = document.getElementById('previewRateOverrideNote');
        var previewRateOverrideText = document.getElementById('previewRateOverrideText');
        if (previewRateOverrideNote && previewRateOverrideText) {
            if ((row.getAttribute('data-rate-override') || '0') === '1') {
                previewRateOverrideText.textContent = 'Rate override: ' + (row.getAttribute('data-rate-override-text') || '');
                previewRateOverrideNote.classList.remove('d-none');
            } else {
                previewRateOverrideNote.classList.add('d-none');
            }
        }

        var previewRoomMixText = row.getAttribute('data-room-mix') || '-';
        document.getElementById('previewRoomMix').textContent = previewRoomMixText;
        var previewTripleNote = document.getElementById('previewTripleNote');
        if (previewTripleNote) {
            if (previewRoomMixText.toLowerCase().indexOf('triple / extra bed') !== -1) {
                previewTripleNote.classList.remove('d-none');
            } else {
                previewTripleNote.classList.add('d-none');
            }
        }
        document.getElementById('previewAgent').textContent = row.getAttribute('data-agent') || '-';
        var previewCreatedByEl = document.getElementById('previewCreatedBy');
        if (previewCreatedByEl) previewCreatedByEl.textContent = row.getAttribute('data-created-by') || '-';
        var previewUpdatedByWrap = document.getElementById('previewUpdatedByWrap');
        var previewUpdatedByEl = document.getElementById('previewUpdatedBy');
        var previewUpdatedOnEl = document.getElementById('previewUpdatedOn');
        var updatedByText = row.getAttribute('data-updated-by') || '';
        var updatedOnText = row.getAttribute('data-updated-on') || '';
        if (previewUpdatedByEl) previewUpdatedByEl.textContent = updatedByText || '-';
        if (previewUpdatedOnEl) previewUpdatedOnEl.textContent = updatedOnText || '-';
        if (previewUpdatedByWrap) previewUpdatedByWrap.classList.toggle('d-none', !updatedByText || updatedByText === '-' || !updatedOnText);
        document.getElementById('previewNationality').textContent = row.getAttribute('data-nationality') || '-';
        var isCancelled = (row.getAttribute('data-is-cancelled') || '0') === '1';
        var isNoShow = (row.getAttribute('data-is-no-show') || '0') === '1';
        document.getElementById('previewStatus').textContent = row.getAttribute('data-status') || '-';
        document.getElementById('previewPayment').textContent = row.getAttribute('data-payment') || '-';

        if (panel) {
            panel.classList.toggle('preview-cancelled', isCancelled || isNoShow);
        }
        var previewPaidEl = document.getElementById('previewPaid');
        if (previewPaidEl) previewPaidEl.textContent = row.getAttribute('data-paid') || '0.00';

        var previewVoidedPaidEl = document.getElementById('previewVoidedPaid');
        var previewVoidedPaidWrap = document.getElementById('previewVoidedPaidWrap');
        var voidedPaidText = row.getAttribute('data-voided-paid') || '0.00';
        if (previewVoidedPaidEl) previewVoidedPaidEl.textContent = voidedPaidText;
        if (previewVoidedPaidWrap) {
            var voidedNumber = parseFloat(String(voidedPaidText).replace(/,/g, '')) || 0;
            previewVoidedPaidWrap.classList.toggle('d-none', voidedNumber <= 0);
        }

        document.getElementById('previewEditBtn').href = '/rooms/modules/reservations/reservation_form.php?id=' + orderId;
        document.getElementById('previewFolioBtn').href = '/rooms/modules/payments/folio_view.php?order_id=' + orderId;
        document.getElementById('previewCommunicationsBtn').href = '/rooms/modules/communications/index.php?order_id=' + orderId;
        document.getElementById('previewPaymentsBtn').href = '/rooms/modules/payments/payments.php?order_id=' + orderId;
        document.getElementById('previewExtrasBtn').href = '/rooms/modules/payments/extras.php?order_id=' + orderId;

        panel.classList.add('show');
        backdrop.classList.add('show');
        panel.setAttribute('aria-hidden', 'false');
    }

    function closePreview() {
        // Move focus out of the panel BEFORE hiding it, otherwise the browser
        // warns that a focused element is being hidden from assistive tech.
        if (panel && panel.contains(document.activeElement) && document.activeElement.blur) {
            document.activeElement.blur();
        }
        panel.classList.remove('show');
        backdrop.classList.remove('show');
        panel.setAttribute('aria-hidden', 'true');
    }

    document.querySelectorAll('.js-preview-row').forEach(function (row) {
        row.addEventListener('click', function (e) {
            if (e.target.closest('a, button, .dropdown, select, input, textarea')) {
                return;
            }
            openPreview(row);
        });
    });

    if (closeBtn) closeBtn.addEventListener('click', closePreview);
    if (backdrop) backdrop.addEventListener('click', closePreview);

    document.addEventListener('keydown', function (e) {
        if (e.key === 'Escape') {
            closePreview();
        }
    });

    document.querySelectorAll('.js-inline-update').forEach(function (select) {
        select.setAttribute('data-old-value', select.value);

        select.addEventListener('click', function (e) {
            e.stopPropagation();
        });

        select.addEventListener('change', function () {
            var el = this;
            var oldValue = el.getAttribute('data-old-value') || el.value;
            var row = el.closest('tr');
            var field = el.getAttribute('data-field');
            var selectedText = el.options[el.selectedIndex] ? el.options[el.selectedIndex].text : '';

            // Cancellation must use the policy modal. Do not allow quick status=0 updates.
            if (field === 'status' && el.value === '0') {
                el.value = oldValue;
                var cancelBtn = row ? row.querySelector('.btn-cancel-booking') : null;
                if (cancelBtn) cancelBtn.click();
                else alert('Use Actions > Cancel Booking so cancellation policy and folio accounting are applied.');
                return;
            }

            var formData = new FormData();
            formData.append('order_id', el.getAttribute('data-order-id'));
            formData.append('field', field);
            formData.append('value', el.value);

            if (csrfToken) {
                formData.append('csrf_token', csrfToken);
            }

            el.disabled = true;
            el.classList.add('inline-saving');

            fetch('/rooms/modules/reservations/update_reservation_quick.php', {
                method: 'POST',
                body: formData,
                credentials: 'same-origin'
            })
            .then(function (res) { return res.json(); })
            .then(function (json) {
                if (!json.ok) {
                    throw new Error(json.message || 'Update failed.');
                }

                el.setAttribute('data-old-value', el.value);

                if (row && field === 'status') {
                    row.className = row.className.replace(/row-status-\d/g, '');
                    row.classList.add('row-status-' + el.value);

                    if (selectedText) {
                        row.setAttribute('data-status', selectedText);
                    }
                }

            })
            .catch(function (err) {
                alert(err.message || 'Update failed.');
                el.value = oldValue;
            })
            .finally(function () {
                el.disabled = false;
                el.classList.remove('inline-saving');
            });
        });
    });

    var cancelModalEl = document.getElementById('cancelBookingModal');
    if (cancelModalEl && typeof bootstrap !== 'undefined') {
        var cancelModal = bootstrap.Modal.getOrCreateInstance(cancelModalEl);
        var orderIdInput = document.getElementById('cancel_order_id');
        var orderNoEl = document.getElementById('cancel_summary_order_no');
        var guestEl = document.getElementById('cancel_summary_guest');
        var totalEl = document.getElementById('cancel_summary_total');
        var balanceDisplayEl = document.getElementById('cancel_current_balance_display');
        var feeHintEl = document.getElementById('cancel_fee_hint');
        var policyActionEl = document.getElementById('cancel_policy_action');
        var adjustmentEl = document.getElementById('cancel_adjustment_amount');
        var chargeEl = document.getElementById('cancel_policy_charge_amount');
        var reasonEl = document.getElementById('cancel_reason');
        var subtitleEl = document.getElementById('cancel_modal_subtitle');
        var previewAdjustmentEl = document.getElementById('cancel_preview_adjustment');
        var previewChargeEl = document.getElementById('cancel_preview_charge');
        var previewFinalEl = document.getElementById('cancel_preview_final');
        var amountRows = document.querySelectorAll('.cancel-policy-amounts');
        var currentBalance = 0;

        function fmtMoney(v) {
            v = parseFloat(v || 0) || 0;
            return v.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
        }

        function refreshCancellationPreview() {
            if (!policyActionEl) return;
            var action = policyActionEl.value;
            var adjustment = 0;
            var charge = 0;
            amountRows.forEach(function (el) { el.classList.toggle('d-none', action !== 'waive_and_charge_policy'); });

            if (action === 'keep_balance') {
                adjustment = 0;
                charge = 0;
                if (feeHintEl) feeHintEl.textContent = 'Existing folio balance remains unchanged. No cancellation transactions will be posted.';
            } else if (action === 'waive_all') {
                adjustment = Math.max(0, currentBalance);
                charge = 0;
                if (adjustmentEl) adjustmentEl.value = adjustment.toFixed(2);
                if (chargeEl) chargeEl.value = '0.00';
                if (feeHintEl) feeHintEl.textContent = 'A cancellation adjustment will waive the full current folio balance.';
            } else {
                adjustment = parseFloat(adjustmentEl ? adjustmentEl.value : 0) || 0;
                charge = parseFloat(chargeEl ? chargeEl.value : 0) || 0;
                adjustment = Math.min(Math.max(0, adjustment), Math.max(0, currentBalance));
                if (feeHintEl) feeHintEl.textContent = 'The selected waiver will be posted first, then the cancellation policy charge will be posted.';
            }

            var finalBalance = currentBalance - adjustment + charge;
            if (previewAdjustmentEl) previewAdjustmentEl.textContent = fmtMoney(adjustment);
            if (previewChargeEl) previewChargeEl.textContent = fmtMoney(charge);
            if (previewFinalEl) previewFinalEl.textContent = fmtMoney(finalBalance);
        }

        document.querySelectorAll('.btn-cancel-booking').forEach(function (btn) {
            btn.addEventListener('click', function (e) {
                e.preventDefault();
                e.stopPropagation();

                currentBalance = parseFloat(String(this.getAttribute('data-grand-total') || '0').replace(/,/g, '')) || 0;
                if (orderIdInput) orderIdInput.value = this.getAttribute('data-order-id') || '';
                if (orderNoEl) orderNoEl.textContent = this.getAttribute('data-order-no') || '-';
                if (guestEl) guestEl.textContent = this.getAttribute('data-guest-name') || '-';
                if (totalEl) totalEl.textContent = fmtMoney(currentBalance);
                if (balanceDisplayEl) balanceDisplayEl.value = fmtMoney(currentBalance);
                if (subtitleEl) subtitleEl.textContent = 'Cancel ' + (this.getAttribute('data-order-no') || 'booking') + ' for ' + (this.getAttribute('data-guest-name') || 'guest') + '.';
                if (reasonEl) reasonEl.value = '';
                if (policyActionEl) policyActionEl.value = 'keep_balance';
                if (adjustmentEl) adjustmentEl.value = currentBalance.toFixed(2);
                if (chargeEl) chargeEl.value = '0.00';

                refreshCancellationPreview();
                cancelModal.show();
            });
        });

        if (policyActionEl) policyActionEl.addEventListener('change', refreshCancellationPreview);
        if (adjustmentEl) adjustmentEl.addEventListener('input', refreshCancellationPreview);
        if (chargeEl) chargeEl.addEventListener('input', refreshCancellationPreview);
    }
});
</script>

<?php require_once __DIR__ . '/apply_rate_widget.php'; ?>
<?php require_once __DIR__ . '/../../includes/footer.php'; ?>