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

$pageTitle = 'Front Desk Dashboard';

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

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

if (!function_exists('fd_table_exists')) {
    function fd_table_exists(PDO $pdo, string $tableName): bool
    {
        static $cache = [];
        if (array_key_exists($tableName, $cache)) return $cache[$tableName];
        try {
            $stmt = $pdo->prepare("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t");
            $stmt->execute([':t' => $tableName]);
            return $cache[$tableName] = ((int)$stmt->fetchColumn() > 0);
        } catch (Throwable $e) {
            return $cache[$tableName] = false;
        }
    }
}

if (!function_exists('fd_column_exists')) {
    function fd_column_exists(PDO $pdo, string $tableName, string $columnName): bool
    {
        // Do not cache this result: this file can ALTER TABLE during the same request.
        try {
            $stmt = $pdo->prepare("SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t AND COLUMN_NAME = :c");
            $stmt->execute([':t' => $tableName, ':c' => $columnName]);
            return ((int)$stmt->fetchColumn() > 0);
        } catch (Throwable $e) {
            return false;
        }
    }
}


if (!function_exists('fd_column_is_textual')) {
    function fd_column_is_textual(PDO $pdo, string $tableName, string $columnName): bool
    {
        try {
            $stmt = $pdo->prepare("SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t AND COLUMN_NAME = :c LIMIT 1");
            $stmt->execute([':t' => $tableName, ':c' => $columnName]);
            $type = strtolower((string)($stmt->fetchColumn() ?: ''));
            return in_array($type, ['varchar','char','text','tinytext','mediumtext','longtext','enum','set'], true);
        } catch (Throwable $e) {
            return false;
        }
    }
}


if (!function_exists('fd_column_is_nullable')) {
    function fd_column_is_nullable(PDO $pdo, string $tableName, string $columnName): bool
    {
        try {
            $stmt = $pdo->prepare("SELECT IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t AND COLUMN_NAME = :c LIMIT 1");
            $stmt->execute([':t' => $tableName, ':c' => $columnName]);
            return strtoupper((string)($stmt->fetchColumn() ?: 'NO')) === 'YES';
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('fd_order_status_text_set_sql')) {
    function fd_order_status_text_set_sql(PDO $pdo, string $statusValue, array &$setParts, array &$params, string $paramName = ':workflow_status'): void
    {
        /*
           NEVER write workflow words like no_show / expected_late_arrival into orders.status.
           In this database orders.status is numeric, so all workflow text must go to orders.booking_status.
        */
        if (!fd_column_exists($pdo, 'orders', 'booking_status')) {
            try {
                $pdo->exec("ALTER TABLE `orders` ADD COLUMN `booking_status` VARCHAR(40) NULL AFTER `status`");
            } catch (Throwable $e) {
                // If ALTER is blocked, skip the text status write. Other no-show audit fields still identify it.
            }
        }

        if (fd_column_exists($pdo, 'orders', 'booking_status')) {
            $setParts[] = "booking_status = {$paramName}";
            $params[$paramName] = $statusValue;
        }
    }
}

if (!function_exists('fd_execute_order_update_safely')) {
    function fd_execute_order_update_safely(PDO $pdo, array $setParts, array $params): void
    {
        // Hard safety: remove any accidental text write to numeric orders.status.
        $safeSetParts = [];
        foreach ($setParts as $part) {
            if (preg_match('/^\s*`?status`?\s*=/i', (string)$part)) {
                continue;
            }
            $safeSetParts[] = $part;
        }
        if (!$safeSetParts) return;

        try {
            $pdo->prepare('UPDATE orders SET ' . implode(', ', $safeSetParts) . ' WHERE id = :id')->execute($params);
        } catch (Throwable $e) {
            // Some legacy triggers or failed old patches may still try to force orders.status='no_show'.
            // Retry with only safe audit columns that do not touch workflow text.
            if (stripos($e->getMessage(), "Incorrect integer value: 'no_show' for column 'status'") !== false) {
                $retry = [];
                $retryParams = [':id' => $params[':id'] ?? 0];
                $allowed = [
                    'no_show_at','no_show_reason','no_show_fee_posted','no_show_policy_action',
                    'no_show_original_balance','no_show_adjustment_amount','no_show_policy_charge',
                    'no_show_final_balance','no_show_inventory_released','expected_late_arrival_at',
                    'expected_late_arrival_reason','room_hold_until','updated_at'
                ];
                foreach ($safeSetParts as $part) {
                    foreach ($allowed as $col) {
                        if (preg_match('/^\s*`?' . preg_quote($col, '/') . '`?\s*=/i', (string)$part)) {
                            $retry[] = $part;
                            break;
                        }
                    }
                }
                if ($retry) {
                    $pdo->prepare('UPDATE orders SET ' . implode(', ', $retry) . ' WHERE id = :id')->execute($params);
                    return;
                }
            }
            throw $e;
        }
    }
}

if (!function_exists('fd_try_add_column')) {
    function fd_try_add_column(PDO $pdo, string $tableName, string $columnName, string $definition): void
    {
        if (fd_column_exists($pdo, $tableName, $columnName)) return;
        try {
            $pdo->exec("ALTER TABLE `" . str_replace('`', '``', $tableName) . "` ADD COLUMN " . $definition);
        } catch (Throwable $e) {
            // Keep the dashboard operational even if ALTER is not allowed.
        }
    }
}

if (!function_exists('fd_current_user_id')) {
    function fd_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('arrival_status_badge')) {
    function arrival_status_badge(string $status): string
    {
        switch ($status) {
            case 'checked_in':
                return '<span class="badge text-bg-success">Checked In</span>';
            case 'partial':
                return '<span class="badge text-bg-warning text-dark">Partial Check-In</span>';
            case 'no_show':
                return '<span class="badge text-bg-danger">No Show</span>';
            default:
                return '<span class="badge text-bg-secondary">Pending</span>';
        }
    }
}

if (!function_exists('departure_status_badge')) {
    function departure_status_badge(string $status): string
    {
        switch ($status) {
            case 'checked_out':
                return '<span class="badge text-bg-success">Checked Out</span>';
            case 'partial':
                return '<span class="badge text-bg-warning text-dark">Partial Check-Out</span>';
            default:
                return '<span class="badge text-bg-secondary">Pending</span>';
        }
    }
}

if (!function_exists('fd_is_no_show_order')) {
    function fd_is_no_show_order(array $row): bool
    {
        $status = strtolower(trim((string)($row['status'] ?? '')));
        $bookingStatus = strtolower(trim((string)($row['booking_status'] ?? '')));
        return in_array($status, ['no_show', 'noshow'], true)
            || in_array($bookingStatus, ['no_show', 'noshow'], true)
            || !empty($row['no_show_at']);
    }
}

if (!function_exists('room_mix_with_triple_sql')) {
    function room_mix_with_triple_sql(): string
    {
        return "
            LEFT JOIN (
                SELECT
                    order_id,
                    GROUP_CONCAT(room_summary ORDER BY sort_title SEPARATOR ', ') AS room_mix,
                    SUM(triple_slots) AS triple_slots,
                    SUM(family_slots) AS family_slots
                FROM (
                    SELECT
                        rmg.order_id,
                        rt.title AS sort_title,
                        COUNT(*) AS total_slots,
                        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
                        ) AS triple_slots,
                        SUM(
                            CASE
                                WHEN LOWER(COALESCE(rt.title,'')) LIKE '%family%'
                                THEN 1 ELSE 0
                            END
                        ) AS family_slots,
                        CONCAT(
                            COALESCE(rt.title, 'No type'),
                            ' 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,
                            CASE
                                WHEN SUM(
                                    CASE
                                        WHEN LOWER(COALESCE(rt.title,'')) LIKE '%family%'
                                        THEN 1 ELSE 0
                                    END
                                ) > 0
                                THEN CONCAT(
                                    ' (',
                                    SUM(
                                        CASE
                                            WHEN LOWER(COALESCE(rt.title,'')) LIKE '%family%'
                                            THEN 1 ELSE 0
                                        END
                                    ),
                                    ' Family)'
                                )
                                ELSE ''
                            END
                        ) AS room_summary
                    FROM order_rooming rmg
                    LEFT JOIN room_types rt ON rt.id = rmg.room_type_id
                    GROUP BY rmg.order_id, rmg.room_type_id, rt.title
                ) mix
                GROUP BY order_id
            ) rmix ON rmix.order_id = o.id
        ";
    }
}

if (!function_exists('render_room_mix_badges')) {
    function render_room_mix_badges(array $row): void
    {
        $roomMix = trim((string)($row['room_mix'] ?? ''));
        $tripleSlots = (int)($row['triple_slots'] ?? 0);
        $familySlots = (int)($row['family_slots'] ?? 0);

        if ($roomMix !== '') {
            echo '<div class="fw-semibold">' . h($roomMix) . '</div>';
        } else {
            echo '<div class="fw-semibold text-muted">Room mix not set</div>';
        }

        echo '<div class="d-flex flex-wrap gap-1 mt-1">';
        if ($tripleSlots > 0) {
            echo '<span class="badge text-bg-warning text-dark"><i class="bi bi-person-plus me-1"></i>' . $tripleSlots . ' Triple / Extra Bed</span>';
        }
        if ($familySlots > 0) {
            echo '<span class="badge text-bg-info"><i class="bi bi-people-fill me-1"></i>' . $familySlots . ' Family</span>';
        }
        echo '</div>';
    }
}

if (!function_exists('fd_next_folio_no_for_property')) {
    function fd_next_folio_no_for_property(PDO $pdo, int $propertyId = 0): string
    {
        $prefix = 'FOL';
        if ($propertyId > 0 && fd_table_exists($pdo, 'properties') && fd_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('fd_get_or_create_master_folio')) {
    function fd_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 master folio.');

        $propertyId = (int)($order['property_id'] ?? 0);
        $guestName = trim(($order['firstname'] ?? '') . ' ' . ($order['lastname'] ?? ''));
        if ($guestName === '') $guestName = 'Guest';
        $folioNo = fd_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('fd_recalculate_folio_totals')) {
    function fd_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 = (float)($totals['total_charges'] ?? 0);
        $payments = (float)($totals['total_payments'] ?? 0);
        $balance = $charges - $payments;

        $pdo->prepare("UPDATE folios SET total_charges=:c, total_payments=:p, balance=:b, updated_at=NOW() WHERE id=:id")
            ->execute([':c' => round($charges,2), ':p' => round($payments,2), ':b' => round($balance,2), ':id' => $folioId]);
    }
}

if (!function_exists('fd_recalculate_order_folios')) {
    function fd_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) {
            fd_recalculate_folio_totals($pdo, (int)$folioId);
        }
    }
}

if (!function_exists('fd_live_order_balance')) {
    function fd_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('fd_insert_folio_transaction')) {
    function fd_insert_folio_transaction(PDO $pdo, array $order, int $folioId, string $txType, string $description, float $debit, float $credit, ?int $sourceId = null): void
    {
        $orderId = (int)($order['id'] ?? 0);
        $propertyId = (int)($order['property_id'] ?? 0);
        $sourceId = $sourceId ?: $orderId;

        $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' => $sourceId,
        ];

        if (fd_column_exists($pdo, 'folio_transactions', 'property_id')) {
            array_unshift($columns, 'property_id');
            array_unshift($values, ':property_id');
            $params[':property_id'] = $propertyId;
        }
        if (fd_column_exists($pdo, 'folio_transactions', 'created_at')) {
            $columns[] = 'created_at';
            $values[] = 'NOW()';
        }
        if (fd_column_exists($pdo, 'folio_transactions', 'created_by')) {
            $columns[] = 'created_by';
            $values[] = ':created_by';
            $params[':created_by'] = fd_current_user_id();
        }

        $sql = 'INSERT INTO folio_transactions (`' . implode('`,`', $columns) . '`) VALUES (' . implode(',', $values) . ')';
        $pdo->prepare($sql)->execute($params);
    }
}

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

        /*
           Clean no-show accounting model:
           - keep_balance: leave the existing folio exactly as it is.
           - waive_all: post one credit adjustment equal to the current folio balance.
           - waive_and_charge_policy: post a selected credit adjustment first, then post the policy/no-show charge.

           Example:
             Original folio balance       1,300
             Cancellation adjustment     -1,050  (credit)
             No Show Policy Charge          250  (debit)
             Final balance                  500
        */
        if ($policyAction === 'keep_balance') {
            // No transaction is posted. Existing charges/payments remain fully auditable.
        } 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 no-show accounting option selected.');
        }

        if ($adjustmentAmount > 0.00001) {
            fd_insert_folio_transaction(
                $pdo,
                $order,
                $folioId,
                'no_show_adjustment',
                'No Show Cancellation Adjustment - ' . $bookingLabel . $reasonSuffix,
                0,
                $adjustmentAmount,
                $orderId
            );
        }

        if ($postedCharge > 0.00001) {
            fd_insert_folio_transaction(
                $pdo,
                $order,
                $folioId,
                'no_show_policy_charge',
                'No Show Policy Charge - ' . $bookingLabel . $reasonSuffix,
                $postedCharge,
                0,
                $orderId
            );
        }

        fd_recalculate_folio_totals($pdo, $folioId);
        fd_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' => fd_live_order_balance($pdo, $orderId),
        ];
    }
}


if (!function_exists('fd_is_late_arrival_order')) {
    function fd_is_late_arrival_order(array $row): bool
    {
        $status = strtolower(trim((string)($row['status'] ?? '')));
        $bookingStatus = strtolower(trim((string)($row['booking_status'] ?? '')));
        return in_array($status, ['expected_late_arrival', 'late_arrival', 'hold_room'], true)
            || in_array($bookingStatus, ['expected_late_arrival', 'late_arrival', 'hold_room'], true)
            || !empty($row['expected_late_arrival_at'])
            || !empty($row['room_hold_until']);
    }
}

if (!function_exists('fd_active_status_filter_sql')) {
    function fd_active_status_filter_sql(string $alias = 'o'): string
    {
        /*
           Active booking filter used by availability checks.
           orders.status is numeric in this installation, so workflow statuses
           must be checked through booking_status / no_show_at as well.
        */
        return "CAST(COALESCE({$alias}.status,1) AS CHAR) NOT IN ('0')
                AND LOWER(COALESCE({$alias}.status,'')) NOT IN ('no_show','noshow','cancelled','canceled','void')
                AND LOWER(COALESCE({$alias}.booking_status,'')) NOT IN ('no_show','noshow','cancelled','canceled','void')
                AND {$alias}.no_show_at IS NULL";
    }
}

if (!function_exists('fd_fetch_order_for_action')) {
    function fd_fetch_order_for_action(PDO $pdo, int $orderId): array
    {
        $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('Booking not found.');
        return $order;
    }
}

if (!function_exists('fd_check_room_availability_for_dates')) {
    function fd_check_room_availability_for_dates(PDO $pdo, int $orderId, string $newCheckIn, string $newCheckOut): array
    {
        if (strtotime($newCheckIn) === false || strtotime($newCheckOut) === false) {
            return ['ok' => false, 'message' => 'Invalid check-in or check-out date.'];
        }
        $newCheckIn = date('Y-m-d', strtotime($newCheckIn));
        $newCheckOut = date('Y-m-d', strtotime($newCheckOut));
        if ($newCheckOut <= $newCheckIn) {
            return ['ok' => false, 'message' => 'Check-out date must be after check-in date.'];
        }

        $stmt = $pdo->prepare("SELECT id, property_id, check_in, check_out FROM orders WHERE id = :id LIMIT 1");
        $stmt->execute([':id' => $orderId]);
        $order = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$order) return ['ok' => false, 'message' => 'Booking not found for availability check.'];

        $roomingSql = "SELECT id, property_id, room_id, room_type_id";
        if (fd_column_exists($pdo, 'order_rooming', 'room_no')) $roomingSql .= ", room_no";
        $roomingSql .= " FROM order_rooming WHERE order_id = :order_id ORDER BY id ASC";
        $stmt = $pdo->prepare($roomingSql);
        $stmt->execute([':order_id' => $orderId]);
        $roomingRows = $stmt->fetchAll(PDO::FETCH_ASSOC);
        if (!$roomingRows) return ['ok' => true, 'message' => 'No room slots are attached to this booking.'];

        foreach ($roomingRows as $slot) {
            $roomId = (int)($slot['room_id'] ?? 0);
            $roomTypeId = (int)($slot['room_type_id'] ?? 0);
            $slotPropertyId = (int)($slot['property_id'] ?? ($order['property_id'] ?? 0));

            if ($roomId > 0) {
                $stmt = $pdo->prepare("\n                    SELECT COUNT(*)\n                    FROM order_rooming rmg\n                    INNER JOIN orders o ON o.id = rmg.order_id\n                    WHERE rmg.order_id <> :order_id\n                      AND rmg.room_id = :room_id\n                      AND DATE(o.check_in) < :new_out\n                      AND DATE(o.check_out) > :new_in\n                      AND " . fd_active_status_filter_sql('o') . "\n                      AND COALESCE(rmg.checked_out_status,0) = 0\n                ");
                $stmt->execute([
                    ':order_id' => $orderId,
                    ':room_id' => $roomId,
                    ':new_in' => $newCheckIn,
                    ':new_out' => $newCheckOut,
                ]);
                if ((int)$stmt->fetchColumn() > 0) {
                    return ['ok' => false, 'message' => 'Allocated room is not available for the requested dates. Please move the guest to another room first.'];
                }
                continue;
            }

            if ($roomTypeId > 0 && fd_table_exists($pdo, 'rooms')) {
                $roomWhere = "room_type_id = :room_type_id";
                $roomParams = [':room_type_id' => $roomTypeId];
                if ($slotPropertyId > 0 && fd_column_exists($pdo, 'rooms', 'property_id')) {
                    $roomWhere .= " AND property_id = :property_id";
                    $roomParams[':property_id'] = $slotPropertyId;
                }
                if (fd_column_exists($pdo, 'rooms', 'is_active')) $roomWhere .= " AND COALESCE(is_active,1)=1";
                if (fd_column_exists($pdo, 'rooms', 'status')) $roomWhere .= " AND LOWER(COALESCE(status,'')) NOT IN ('blocked','out_of_order','ooo','maintenance')";

                $stmt = $pdo->prepare("SELECT COUNT(*) FROM rooms WHERE {$roomWhere}");
                $stmt->execute($roomParams);
                $totalRooms = (int)$stmt->fetchColumn();

                $stmt = $pdo->prepare("\n                    SELECT COUNT(DISTINCT rmg.room_id)\n                    FROM order_rooming rmg\n                    INNER JOIN orders o ON o.id = rmg.order_id\n                    WHERE rmg.order_id <> :order_id\n                      AND rmg.room_type_id = :room_type_id\n                      AND rmg.room_id IS NOT NULL\n                      AND rmg.room_id > 0\n                      AND DATE(o.check_in) < :new_out\n                      AND DATE(o.check_out) > :new_in\n                      AND " . fd_active_status_filter_sql('o') . "\n                      AND COALESCE(rmg.checked_out_status,0) = 0\n                      " . ($slotPropertyId > 0 ? " AND o.property_id = :property_id " : "") . "\n                ");
                $params = [
                    ':order_id' => $orderId,
                    ':room_type_id' => $roomTypeId,
                    ':new_in' => $newCheckIn,
                    ':new_out' => $newCheckOut,
                ];
                if ($slotPropertyId > 0) $params[':property_id'] = $slotPropertyId;
                $stmt->execute($params);
                $occupiedRooms = (int)$stmt->fetchColumn();
                $availableRooms = max(0, $totalRooms - $occupiedRooms);
                if ($availableRooms <= 0) {
                    return ['ok' => false, 'message' => 'No available room remains for one of the required room types in the requested date range.'];
                }
            }
        }

        return ['ok' => true, 'message' => 'Availability confirmed.'];
    }
}

if (!function_exists('fd_update_booking_dates')) {
    function fd_update_booking_dates(PDO $pdo, int $orderId, string $newCheckIn, string $newCheckOut): void
    {
        $newCheckIn = date('Y-m-d', strtotime($newCheckIn));
        $newCheckOut = date('Y-m-d', strtotime($newCheckOut));
        $set = ['check_in = :check_in', 'check_out = :check_out'];
        if (fd_column_exists($pdo, 'orders', 'updated_at')) $set[] = 'updated_at = NOW()';
        $pdo->prepare('UPDATE orders SET ' . implode(', ', $set) . ' WHERE id = :id')
            ->execute([':check_in' => $newCheckIn, ':check_out' => $newCheckOut, ':id' => $orderId]);

        $rSet = [];
        $params = [':order_id' => $orderId];
        if (fd_column_exists($pdo, 'order_rooming', 'check_in')) { $rSet[] = 'check_in = :check_in'; $params[':check_in'] = $newCheckIn; }
        if (fd_column_exists($pdo, 'order_rooming', 'check_out')) { $rSet[] = 'check_out = :check_out'; $params[':check_out'] = $newCheckOut; }
        if ($rSet) {
            $pdo->prepare('UPDATE order_rooming SET ' . implode(', ', $rSet) . ' WHERE order_id = :order_id')->execute($params);
        }
    }
}

if (!function_exists('fd_release_no_show_inventory')) {
    function fd_release_no_show_inventory(PDO $pdo, int $orderId, string $reason = ''): void
    {
        /*
           Release rules for FULL NO-SHOW:
           1) Do not depend only on rooms.status.
           2) Mark order_rooming as released/closed so availability and planners can ignore it.
           3) If room_id is nullable, clear it. If it is NOT NULL, keep it for history but close the slot.
           4) If rooming has its own dates, collapse them to check_in = check_out so date-overlap checks stop blocking the room.
           5) Mark orders.no_show_inventory_released = 1.
        */

        $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) {}

        // Add release audit columns where permissions allow.
        fd_try_add_column($pdo, 'order_rooming', 'released_status', '`released_status` TINYINT(1) NOT NULL DEFAULT 0');
        fd_try_add_column($pdo, 'order_rooming', 'released_at', '`released_at` DATETIME NULL');
        fd_try_add_column($pdo, 'order_rooming', 'release_reason', '`release_reason` VARCHAR(255) NULL');

        $updates = [];

        $roomIdCanBeNull = fd_column_exists($pdo, 'order_rooming', 'room_id')
            && fd_column_is_nullable($pdo, 'order_rooming', 'room_id');

        if ($roomIdCanBeNull) {
            $updates[] = 'room_id = NULL';
        }

        if (fd_column_exists($pdo, 'order_rooming', 'room_no') && fd_column_is_nullable($pdo, 'order_rooming', 'room_no')) {
            $updates[] = 'room_no = NULL';
        }

        if (fd_column_exists($pdo, 'order_rooming', 'checked_in_status')) {
            $updates[] = 'checked_in_status = 0';
        }

        if (fd_column_exists($pdo, 'order_rooming', 'checked_out_status')) {
            // Important: most availability/planner screens already ignore checked_out rows.
            $updates[] = 'checked_out_status = 1';
        }

        if (fd_column_exists($pdo, 'order_rooming', 'released_status')) {
            $updates[] = 'released_status = 1';
        }

        if (fd_column_exists($pdo, 'order_rooming', 'released_at')) {
            $updates[] = 'released_at = NOW()';
        }

        if (fd_column_exists($pdo, 'order_rooming', 'release_reason')) {
            $updates[] = 'release_reason = :release_reason';
        }

        /*
           Very important fallback:
           Some planner/calendar/availability queries do not check checked_out_status yet.
           Many of them only check order_rooming.check_in/check_out overlap.
           Collapsing rooming dates prevents this released no-show allocation from blocking inventory,
           while the original booking dates remain on orders for audit/history.
        */
        if (fd_column_exists($pdo, 'order_rooming', 'check_in') && fd_column_exists($pdo, 'order_rooming', 'check_out')) {
            $updates[] = 'check_out = check_in';
        }

        if (fd_column_exists($pdo, 'order_rooming', 'updated_at')) {
            $updates[] = 'updated_at = NOW()';
        }

        if ($updates) {
            $params = [':order_id' => $orderId];
            if (in_array('release_reason = :release_reason', $updates, true)) {
                $params[':release_reason'] = $reason !== '' ? $reason : 'Released due to no-show';
            }

            $pdo->prepare('UPDATE order_rooming SET ' . implode(', ', $updates) . ' WHERE order_id = :order_id')
                ->execute($params);
        }

        if (fd_column_exists($pdo, 'orders', 'no_show_inventory_released')) {
            $pdo->prepare("UPDATE orders SET no_show_inventory_released = 1 WHERE id = :id")
                ->execute([':id' => $orderId]);
        }

        /*
           Put actual room records back to available only if no other active,
           non-released allocation is using the same room today.
        */
        if ($roomIds && fd_table_exists($pdo, 'rooms')) {
            foreach ($roomIds as $roomId) {
                $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 " . fd_active_status_filter_sql('o') . "
                      AND COALESCE(rmg.checked_out_status,0) = 0
                      AND COALESCE(rmg.released_status,0) = 0
                ");
                $stmt->execute([':room_id' => $roomId, ':order_id' => $orderId]);
                $stillOccupied = (int)$stmt->fetchColumn() > 0;

                if (!$stillOccupied) {
                    $roomSet = [];
                    if (fd_column_exists($pdo, 'rooms', 'status')) {
                        $roomSet[] = "status = 'available'";
                    }
                    if (fd_column_exists($pdo, 'rooms', 'housekeeping_status')) {
                        $roomSet[] = "housekeeping_status = 'vacant'";
                    }
                    if (fd_column_exists($pdo, 'rooms', 'updated_at')) {
                        $roomSet[] = "updated_at = NOW()";
                    }
                    if ($roomSet) {
                        $pdo->prepare('UPDATE rooms SET ' . implode(', ', $roomSet) . ' WHERE id = :id')
                            ->execute([':id' => $roomId]);
                    }
                }
            }
        }
    }
}

$today = date('Y-m-d');
$propertyId = (int)($_GET['property_id'] ?? 0);
$search = trim((string)($_GET['q'] ?? ''));

$properties = [];
$errors = [];
$success = '';
$arrivals = [];
$inHouse = [];
$departures = [];

try {
    if (fd_table_exists($pdo, 'orders')) {
        fd_try_add_column($pdo, 'orders', 'booking_status', "`booking_status` VARCHAR(40) NULL AFTER `status`");
        fd_try_add_column($pdo, 'orders', 'no_show_at', '`no_show_at` DATETIME NULL AFTER `booking_status`');
        fd_try_add_column($pdo, 'orders', 'no_show_reason', '`no_show_reason` VARCHAR(255) NULL AFTER `no_show_at`');
        fd_try_add_column($pdo, 'orders', 'no_show_fee_posted', '`no_show_fee_posted` TINYINT(1) NOT NULL DEFAULT 0 AFTER `no_show_reason`');
        fd_try_add_column($pdo, 'orders', 'no_show_policy_action', '`no_show_policy_action` VARCHAR(40) NULL AFTER `no_show_fee_posted`');
        fd_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`');
        fd_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`');
        fd_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`');
        fd_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`');
        fd_try_add_column($pdo, 'orders', 'no_show_inventory_released', '`no_show_inventory_released` TINYINT(1) NOT NULL DEFAULT 0 AFTER `no_show_fee_posted`');
        fd_try_add_column($pdo, 'orders', 'expected_late_arrival_at', '`expected_late_arrival_at` DATETIME NULL AFTER `no_show_inventory_released`');
        fd_try_add_column($pdo, 'orders', 'expected_late_arrival_reason', '`expected_late_arrival_reason` VARCHAR(255) NULL AFTER `expected_late_arrival_at`');
        fd_try_add_column($pdo, 'orders', 'room_hold_until', '`room_hold_until` DATETIME NULL AFTER `expected_late_arrival_reason`');
    }
} catch (Throwable $e) {}

if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
    try {
        if (function_exists('verify_csrf')) verify_csrf();
        $action = (string)($_POST['action'] ?? '');

        if ($action === 'mark_no_show') {
            $orderId = (int)($_POST['order_id'] ?? 0);
            $reason = trim((string)($_POST['reason'] ?? 'Guest did not arrive.'));
            $policyAction = trim((string)($_POST['policy_action'] ?? 'keep_balance'));
            $policyChargeAmount = (float)($_POST['policy_charge_amount'] ?? 0);
            $policyAdjustmentAmount = (float)($_POST['policy_adjustment_amount'] ?? 0);
            $validPolicyActions = ['keep_balance', 'waive_all', 'waive_and_charge_policy'];
            if (!in_array($policyAction, $validPolicyActions, true)) {
                throw new RuntimeException('Invalid no-show accounting option selected.');
            }

            if ($orderId <= 0) throw new RuntimeException('Invalid booking selected.');

            $stmt = $pdo->prepare("
                SELECT o.*, 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.');

            $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 booking already has checked-in room slots and cannot be marked as no-show.');
            }

            $pdo->beginTransaction();

            $setParts = [];
            $params = [':id' => $orderId];
            fd_order_status_text_set_sql($pdo, 'no_show', $setParts, $params, ':workflow_status');
            if (fd_column_exists($pdo, 'orders', 'no_show_at')) {
                $setParts[] = "no_show_at = NOW()";
            }
            if (fd_column_exists($pdo, 'orders', 'no_show_reason')) {
                $setParts[] = "no_show_reason = :reason";
                $params[':reason'] = $reason;
            }
            if (fd_column_exists($pdo, 'orders', 'no_show_fee_posted')) {
                $setParts[] = "no_show_fee_posted = :fee_posted";
                $params[':fee_posted'] = ($policyAction === 'waive_and_charge_policy' && $policyChargeAmount > 0) ? 1 : 0;
            }
            if (fd_column_exists($pdo, 'orders', 'no_show_policy_action')) {
                $setParts[] = "no_show_policy_action = :policy_action";
                $params[':policy_action'] = $policyAction;
            }
            if (fd_column_exists($pdo, 'orders', 'updated_at')) {
                $setParts[] = "updated_at = NOW()";
            }
            if ($setParts) {
                $pdo->prepare("UPDATE orders SET " . implode(', ', $setParts) . " WHERE id = :id")->execute($params);
            }

            $releaseInventory = !empty($_POST['release_inventory']) ? 1 : 0;

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

            $policyUpdateParts = [];
            $policyUpdateParams = [':id' => $orderId];
            if (fd_column_exists($pdo, 'orders', 'no_show_original_balance')) {
                $policyUpdateParts[] = 'no_show_original_balance = :original_balance';
                $policyUpdateParams[':original_balance'] = $policyResult['original_balance'];
            }
            if (fd_column_exists($pdo, 'orders', 'no_show_adjustment_amount')) {
                $policyUpdateParts[] = 'no_show_adjustment_amount = :adjustment_amount';
                $policyUpdateParams[':adjustment_amount'] = $policyResult['adjustment_amount'];
            }
            if (fd_column_exists($pdo, 'orders', 'no_show_policy_charge')) {
                $policyUpdateParts[] = 'no_show_policy_charge = :policy_charge';
                $policyUpdateParams[':policy_charge'] = $policyResult['policy_charge'];
            }
            if (fd_column_exists($pdo, 'orders', 'no_show_final_balance')) {
                $policyUpdateParts[] = 'no_show_final_balance = :final_balance';
                $policyUpdateParams[':final_balance'] = $policyResult['final_balance'];
            }
            if ($policyUpdateParts) {
                fd_execute_order_update_safely($pdo, $policyUpdateParts, $policyUpdateParams);
            }

            if ($releaseInventory) {
                fd_release_no_show_inventory($pdo, $orderId, $reason);
                if (fd_column_exists($pdo, 'orders', 'no_show_inventory_released')) {
                    $pdo->prepare("UPDATE orders SET no_show_inventory_released = 1 WHERE id = :id")->execute([':id' => $orderId]);
                }
            }

            $pdo->commit();
            header('Location: frontdesk_dashboard.php?no_show=1' . ($propertyId > 0 ? '&property_id=' . (int)$propertyId : ''));
            exit;
        }

        if ($action === 'late_arrival') {
            $orderId = (int)($_POST['order_id'] ?? 0);
            $newCheckIn = trim((string)($_POST['new_check_in'] ?? ''));
            $newCheckOut = trim((string)($_POST['new_check_out'] ?? ''));
            $reason = trim((string)($_POST['reason'] ?? 'Expected late arrival.'));
            $holdRoom = !empty($_POST['hold_room']) ? 1 : 0;
            if ($orderId <= 0) throw new RuntimeException('Invalid booking selected.');
            $order = fd_fetch_order_for_action($pdo, $orderId);
            if ($newCheckIn === '') throw new RuntimeException('Please enter the new arrival date.');
            if ($newCheckOut === '') $newCheckOut = (string)($order['check_out'] ?? '');
            if ($newCheckOut === '') throw new RuntimeException('Please enter the check-out date.');
            $availability = fd_check_room_availability_for_dates($pdo, $orderId, $newCheckIn, $newCheckOut);
            if (!$availability['ok']) throw new RuntimeException($availability['message']);

            $pdo->beginTransaction();
            fd_update_booking_dates($pdo, $orderId, $newCheckIn, $newCheckOut);
            $setParts = [];
            $params = [':id' => $orderId];
            fd_order_status_text_set_sql($pdo, 'expected_late_arrival', $setParts, $params, ':workflow_status');
            if (fd_column_exists($pdo, 'orders', 'expected_late_arrival_at')) $setParts[] = "expected_late_arrival_at = NOW()";
            if (fd_column_exists($pdo, 'orders', 'expected_late_arrival_reason')) { $setParts[] = "expected_late_arrival_reason = :reason"; $params[':reason'] = $reason; }
            if (fd_column_exists($pdo, 'orders', 'room_hold_until') && $holdRoom) { $setParts[] = "room_hold_until = :hold_until"; $params[':hold_until'] = date('Y-m-d 23:59:59', strtotime($newCheckIn)); }
            if (fd_column_exists($pdo, 'orders', 'updated_at')) $setParts[] = "updated_at = NOW()";
            if ($setParts) fd_execute_order_update_safely($pdo, $setParts, $params);
            $pdo->commit();
            header('Location: frontdesk_dashboard.php?late_arrival=1' . ($propertyId > 0 ? '&property_id=' . (int)$propertyId : ''));
            exit;
        }

        if ($action === 'hold_room') {
            $orderId = (int)($_POST['order_id'] ?? 0);
            $reason = trim((string)($_POST['reason'] ?? 'Room held for delayed guest.'));
            $holdUntil = trim((string)($_POST['hold_until'] ?? date('Y-m-d 23:59:59')));
            if ($orderId <= 0) throw new RuntimeException('Invalid booking selected.');
            if (strtotime($holdUntil) === false) throw new RuntimeException('Invalid hold-until date/time.');
            $pdo->beginTransaction();
            $setParts = [];
            $params = [':id' => $orderId];
            fd_order_status_text_set_sql($pdo, 'expected_late_arrival', $setParts, $params, ':workflow_status');
            if (fd_column_exists($pdo, 'orders', 'expected_late_arrival_at')) $setParts[] = "expected_late_arrival_at = NOW()";
            if (fd_column_exists($pdo, 'orders', 'expected_late_arrival_reason')) { $setParts[] = "expected_late_arrival_reason = :reason"; $params[':reason'] = $reason; }
            if (fd_column_exists($pdo, 'orders', 'room_hold_until')) { $setParts[] = "room_hold_until = :hold_until"; $params[':hold_until'] = date('Y-m-d H:i:s', strtotime($holdUntil)); }
            if (fd_column_exists($pdo, 'orders', 'updated_at')) $setParts[] = "updated_at = NOW()";
            if ($setParts) fd_execute_order_update_safely($pdo, $setParts, $params);
            $pdo->commit();
            header('Location: frontdesk_dashboard.php?hold_room=1' . ($propertyId > 0 ? '&property_id=' . (int)$propertyId : ''));
            exit;
        }

        if ($action === 'extend_stay') {
            $orderId = (int)($_POST['order_id'] ?? 0);
            $newCheckOut = trim((string)($_POST['new_check_out'] ?? ''));
            if ($orderId <= 0) throw new RuntimeException('Invalid booking selected.');
            $order = fd_fetch_order_for_action($pdo, $orderId);
            $newCheckIn = date('Y-m-d', strtotime((string)$order['check_in']));
            if ($newCheckOut === '' || strtotime($newCheckOut) === false) throw new RuntimeException('Please enter a valid new check-out date.');
            if (date('Y-m-d', strtotime($newCheckOut)) <= date('Y-m-d', strtotime((string)$order['check_out']))) {
                throw new RuntimeException('The new check-out date must be after the current check-out date.');
            }
            $availability = fd_check_room_availability_for_dates($pdo, $orderId, $newCheckIn, $newCheckOut);
            if (!$availability['ok']) throw new RuntimeException($availability['message']);
            $pdo->beginTransaction();
            fd_update_booking_dates($pdo, $orderId, $newCheckIn, $newCheckOut);
            if (fd_column_exists($pdo, 'orders', 'updated_at')) $pdo->prepare('UPDATE orders SET updated_at = NOW() WHERE id = :id')->execute([':id' => $orderId]);
            $pdo->commit();
            header('Location: frontdesk_dashboard.php?extended=1' . ($propertyId > 0 ? '&property_id=' . (int)$propertyId : ''));
            exit;
        }
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        $errors[] = $e->getMessage();
    }
}

if (isset($_GET['no_show']) && (int)$_GET['no_show'] === 1) {
    $success = 'Booking marked as no-show successfully and folio totals recalculated.';
}
if (isset($_GET['late_arrival']) && (int)$_GET['late_arrival'] === 1) {
    $success = 'Late arrival saved successfully after availability check.';
}
if (isset($_GET['hold_room']) && (int)$_GET['hold_room'] === 1) {
    $success = 'Room hold saved successfully.';
}
if (isset($_GET['extended']) && (int)$_GET['extended'] === 1) {
    $success = 'Stay extension saved successfully after availability check.';
}

try {
    $stmt = $pdo->query("
        SELECT id, name, code
        FROM properties
        WHERE is_active = 1
        ORDER BY name ASC
    ");
    $properties = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
    $properties = [];
}

function build_search_filter(string $alias = 'o'): string
{
    return "
        AND (
            {$alias}.order_no LIKE ?
            OR CAST({$alias}.id AS CHAR) LIKE ?
            OR g.firstname LIKE ?
            OR g.lastname LIKE ?
            OR CONCAT(COALESCE(a.firstname,''), ' ', COALESCE(a.lastname,'')) LIKE ?
        )
    ";
}

function build_property_filter(string $alias = 'o'): string
{
    return " AND {$alias}.property_id = ? ";
}

$bookingStatusExists = fd_column_exists($pdo, 'orders', 'booking_status');
$statusIsText = fd_column_exists($pdo, 'orders', 'status') && fd_column_is_textual($pdo, 'orders', 'status');
$workflowStatusExpr = $bookingStatusExists ? "LOWER(COALESCE(o.booking_status,''))" : ($statusIsText ? "LOWER(COALESCE(o.status,''))" : "''");
$noShowStatusFilter = " AND CAST(COALESCE(o.status,1) AS CHAR) NOT IN ('0') AND LOWER(COALESCE(o.status,'')) NOT IN ('cancelled','canceled') AND {$workflowStatusExpr} NOT IN ('cancelled','canceled') ";
$notNoShowWorkflowSql = " AND CAST(COALESCE(o.status,1) AS CHAR) NOT IN ('0') AND LOWER(COALESCE(o.status,'')) NOT IN ('cancelled','canceled') AND {$workflowStatusExpr} NOT IN ('no_show','noshow','cancelled','canceled') ";

/* -----------------------------
   ARRIVALS TODAY
----------------------------- */
try {
    $sql = "
        SELECT
            o.*,
            p.name AS property_name,
            p.code AS property_code,
            rt.title AS room_type,
            rmix.room_mix,
            COALESCE(rmix.triple_slots, 0) AS triple_slots,
            COALESCE(rmix.family_slots, 0) AS family_slots,
            g.firstname,
            g.lastname,
            CONCAT(COALESCE(a.firstname,''), ' ', COALESCE(a.lastname,'')) AS agent_name,

            (SELECT COUNT(*) FROM order_rooming rmg WHERE rmg.order_id = o.id) AS room_slots_total,
            (SELECT COUNT(*) FROM order_rooming rmg WHERE rmg.order_id = o.id AND COALESCE(rmg.checked_in_status,0) = 1) AS room_slots_checked_in,
            (SELECT COUNT(*) FROM order_rooming rmg WHERE rmg.order_id = o.id AND COALESCE(rmg.checked_out_status,0) = 1) AS room_slots_checked_out,
            (SELECT COALESCE(SUM(ft.debit - ft.credit), 0) FROM folio_transactions ft WHERE ft.order_id = o.id AND COALESCE(ft.is_voided, 0) = 0) AS folio_total_balance,
            (SELECT COALESCE(SUM(CASE WHEN LOWER(COALESCE(ft.tx_type,'')) IN ('no_show_policy_charge','no_show_fee') THEN ft.debit ELSE 0 END),0) FROM folio_transactions ft WHERE ft.order_id=o.id AND COALESCE(ft.is_voided,0)=0) AS no_show_policy_charge_amount,
            (SELECT COALESCE(SUM(CASE WHEN LOWER(COALESCE(ft.tx_type,''))='no_show_adjustment' THEN ft.credit ELSE 0 END),0) FROM folio_transactions ft WHERE ft.order_id=o.id AND COALESCE(ft.is_voided,0)=0) AS no_show_adjustment_amount_live
        FROM orders o
        LEFT JOIN properties p ON p.id = o.property_id
        LEFT JOIN room_types rt ON rt.id = o.room_type_id
        LEFT JOIN guests g ON g.id = o.guest_id
        LEFT JOIN agents a ON a.id = o.agent_id
        " . room_mix_with_triple_sql() . "
        WHERE DATE(o.check_in) = ?
        $noShowStatusFilter
    ";

    $params = [$today];

    if ($propertyId > 0) { $sql .= build_property_filter('o'); $params[] = $propertyId; }
    if ($search !== '') {
        $sql .= build_search_filter('o');
        $searchLike = '%' . $search . '%';
        array_push($params, $searchLike, $searchLike, $searchLike, $searchLike, $searchLike);
    }

    $sql .= " ORDER BY p.name ASC, o.check_in ASC, o.id DESC ";
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    $arrivals = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
    $errors[] = 'Arrivals: ' . $e->getMessage();
}

/* -----------------------------
   IN HOUSE
----------------------------- */
try {
    $sql = "
        SELECT
            o.*,
            p.name AS property_name,
            p.code AS property_code,
            rt.title AS room_type,
            rmix.room_mix,
            COALESCE(rmix.triple_slots, 0) AS triple_slots,
            COALESCE(rmix.family_slots, 0) AS family_slots,
            g.firstname,
            g.lastname,
            CONCAT(COALESCE(a.firstname,''), ' ', COALESCE(a.lastname,'')) AS agent_name,
            (SELECT COUNT(*) FROM order_rooming rmg WHERE rmg.order_id = o.id) AS room_slots_total,
            (SELECT COUNT(*) FROM order_rooming rmg WHERE rmg.order_id = o.id AND COALESCE(rmg.checked_in_status,0) = 1) AS room_slots_checked_in,
            (SELECT COUNT(*) FROM order_rooming rmg WHERE rmg.order_id = o.id AND COALESCE(rmg.checked_out_status,0) = 1) AS room_slots_checked_out,
            (SELECT COALESCE(SUM(ft.debit - ft.credit), 0) FROM folio_transactions ft WHERE ft.order_id = o.id AND COALESCE(ft.is_voided, 0) = 0) AS folio_total_balance,
            (SELECT COALESCE(SUM(CASE WHEN LOWER(COALESCE(ft.tx_type,'')) IN ('no_show_policy_charge','no_show_fee') THEN ft.debit ELSE 0 END),0) FROM folio_transactions ft WHERE ft.order_id=o.id AND COALESCE(ft.is_voided,0)=0) AS no_show_policy_charge_amount,
            (SELECT COALESCE(SUM(CASE WHEN LOWER(COALESCE(ft.tx_type,''))='no_show_adjustment' THEN ft.credit ELSE 0 END),0) FROM folio_transactions ft WHERE ft.order_id=o.id AND COALESCE(ft.is_voided,0)=0) AS no_show_adjustment_amount_live
        FROM orders o
        LEFT JOIN properties p ON p.id = o.property_id
        LEFT JOIN room_types rt ON rt.id = o.room_type_id
        LEFT JOIN guests g ON g.id = o.guest_id
        LEFT JOIN agents a ON a.id = o.agent_id
        " . room_mix_with_triple_sql() . "
        WHERE DATE(o.check_in) <= ?
          AND DATE(o.check_out) >= ?
          $notNoShowWorkflowSql
    ";

    $params = [$today, $today];
    if ($propertyId > 0) { $sql .= build_property_filter('o'); $params[] = $propertyId; }
    if ($search !== '') {
        $sql .= build_search_filter('o');
        $searchLike = '%' . $search . '%';
        array_push($params, $searchLike, $searchLike, $searchLike, $searchLike, $searchLike);
    }

    $sql .= " ORDER BY p.name ASC, o.check_out ASC, o.id DESC ";
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    $inHouse = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
    $errors[] = 'In House: ' . $e->getMessage();
}

/* -----------------------------
   DEPARTURES TODAY
----------------------------- */
try {
    $sql = "
        SELECT
            o.*,
            p.name AS property_name,
            p.code AS property_code,
            rt.title AS room_type,
            rmix.room_mix,
            COALESCE(rmix.triple_slots, 0) AS triple_slots,
            COALESCE(rmix.family_slots, 0) AS family_slots,
            g.firstname,
            g.lastname,
            CONCAT(COALESCE(a.firstname,''), ' ', COALESCE(a.lastname,'')) AS agent_name,
            (SELECT COUNT(*) FROM order_rooming rmg WHERE rmg.order_id = o.id) AS room_slots_total,
            (SELECT COUNT(*) FROM order_rooming rmg WHERE rmg.order_id = o.id AND COALESCE(rmg.checked_in_status,0) = 1) AS room_slots_checked_in,
            (SELECT COUNT(*) FROM order_rooming rmg WHERE rmg.order_id = o.id AND COALESCE(rmg.checked_out_status,0) = 1) AS room_slots_checked_out,
            (SELECT COALESCE(SUM(ft.debit - ft.credit), 0) FROM folio_transactions ft WHERE ft.order_id = o.id AND COALESCE(ft.is_voided, 0) = 0) AS folio_total_balance,
            (SELECT COALESCE(SUM(CASE WHEN LOWER(COALESCE(ft.tx_type,'')) IN ('no_show_policy_charge','no_show_fee') THEN ft.debit ELSE 0 END),0) FROM folio_transactions ft WHERE ft.order_id=o.id AND COALESCE(ft.is_voided,0)=0) AS no_show_policy_charge_amount,
            (SELECT COALESCE(SUM(CASE WHEN LOWER(COALESCE(ft.tx_type,''))='no_show_adjustment' THEN ft.credit ELSE 0 END),0) FROM folio_transactions ft WHERE ft.order_id=o.id AND COALESCE(ft.is_voided,0)=0) AS no_show_adjustment_amount_live
        FROM orders o
        LEFT JOIN properties p ON p.id = o.property_id
        LEFT JOIN room_types rt ON rt.id = o.room_type_id
        LEFT JOIN guests g ON g.id = o.guest_id
        LEFT JOIN agents a ON a.id = o.agent_id
        " . room_mix_with_triple_sql() . "
        WHERE DATE(o.check_out) = ?
          $notNoShowWorkflowSql
    ";

    $params = [$today];
    if ($propertyId > 0) { $sql .= build_property_filter('o'); $params[] = $propertyId; }
    if ($search !== '') {
        $sql .= build_search_filter('o');
        $searchLike = '%' . $search . '%';
        array_push($params, $searchLike, $searchLike, $searchLike, $searchLike, $searchLike);
    }

    $sql .= " ORDER BY p.name ASC, o.check_out ASC, o.id DESC ";
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    $departures = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
    $errors[] = 'Departures: ' . $e->getMessage();
}

$totalNoShowsToday = 0;
foreach ($arrivals as $aRow) {
    if (fd_is_no_show_order($aRow)) $totalNoShowsToday++;
}

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

<div class="hero-band frontdesk-compact-hero">
    <div class="row g-4 align-items-center">
        <div class="col-lg-8">
            <div class="page-eyebrow text-white-50"><i class="bi bi-display"></i> Front Desk</div>
            <div class="hero-title mt-2">Front Desk Dashboard</div>
            <div class="hero-subtitle mt-2">
                Monitor arrivals, in-house guests, no-shows, and departures from one operational screen.
            </div>
        </div>
        <div class="col-lg-4 text-lg-end">
            <a href="/rooms/modules/reservations/reservation_list.php" class="btn btn-outline-light">
                <i class="bi bi-arrow-left me-2"></i>Reservations
            </a>
        </div>
    </div>
</div>

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

<?php if ($errors): ?>
    <div class="alert alert-danger">
        <div class="fw-bold mb-2">Please fix the following:</div>
        <ul class="mb-0">
            <?php foreach ($errors as $err): ?>
                <li><?php echo h($err); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<div class="row g-2 mb-2 frontdesk-metrics">
    <div class="col-sm-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">Arrivals Today</div><div class="metric-value"><?php echo number_format(count($arrivals)); ?></div></div>
        </div>
    </div>
    <div class="col-sm-3">
        <div class="metric-card">
            <div class="metric-icon bg-primary-subtle text-primary"><i class="bi bi-house-door"></i></div>
            <div><div class="metric-label">In House</div><div class="metric-value"><?php echo number_format(count($inHouse)); ?></div></div>
        </div>
    </div>
    <div class="col-sm-3">
        <div class="metric-card">
            <div class="metric-icon bg-danger-subtle text-danger"><i class="bi bi-person-x"></i></div>
            <div><div class="metric-label">No Shows Today</div><div class="metric-value"><?php echo number_format($totalNoShowsToday); ?></div></div>
        </div>
    </div>
    <div class="col-sm-3">
        <div class="metric-card">
            <div class="metric-icon bg-danger-subtle text-danger"><i class="bi bi-door-closed"></i></div>
            <div><div class="metric-label">Departures Today</div><div class="metric-value"><?php echo number_format(count($departures)); ?></div></div>
        </div>
    </div>
</div>

<div class="card panel-card mb-2 frontdesk-filter-card">
    <div class="card-header py-2"><h6 class="mb-0 fw-bold">Search & Filters</h6></div>
    <div class="card-body">
        <form method="get" class="row g-2 align-items-end">
            <div class="col-md-4">
                <label class="form-label">Property</label>
                <select name="property_id" class="form-select">
                    <option value="0">All properties</option>
                    <?php foreach ($properties as $property): ?>
                        <option value="<?php echo (int)$property['id']; ?>" <?php echo $propertyId === (int)$property['id'] ? 'selected' : ''; ?>>
                            <?php echo h($property['name']); ?><?php echo !empty($property['code']) ? ' - ' . h($property['code']) : ''; ?>
                        </option>
                    <?php endforeach; ?>
                </select>
            </div>
            <div class="col-md-7">
                <label class="form-label">Search</label>
                <input type="text" name="q" class="form-control" value="<?php echo h($search); ?>" placeholder="Booking no, guest, agent...">
            </div>
            <div class="col-md-1 d-grid"><button class="btn btn-primary">Go</button></div>
        </form>
    </div>
</div>

<?php
function render_booking_table(array $rows, string $mode = 'arrival')
{
    $nowHour = (int)date('H');
    ?>
    <div class="table-responsive frontdesk-table-wrap">
        <table class="table table-modern align-middle mb-0 frontdesk-table">
            <thead>
                <tr>
                    <th>Booking</th><th>Property</th><th>Guest</th><th>Agent</th><th>Stay</th><th>Rooms / Pax</th><th>Status</th><th class="text-end">Folio Balance</th><th class="text-end">Actions</th>
                </tr>
            </thead>
            <tbody>
                <?php if (!$rows): ?>
                    <tr><td colspan="9" class="text-center text-muted py-5">No records found.</td></tr>
                <?php else: ?>
                    <?php foreach ($rows as $row): ?>
                        <?php
                        $guestName = trim(($row['firstname'] ?? '') . ' ' . ($row['lastname'] ?? ''));
                        if ($guestName === '') $guestName = 'Guest';
                        $agentName = trim((string)($row['agent_name'] ?? ''));
                        if ($agentName === '') $agentName = 'Direct';
                        $propertyLabel = trim((string)($row['property_name'] ?? ''));
                        if (!empty($row['property_code'])) $propertyLabel .= ($propertyLabel !== '' ? ' (' . $row['property_code'] . ')' : $row['property_code']);
                        $balance = (float)($row['folio_total_balance'] ?? 0);
                        $slotsTotal = (int)($row['room_slots_total'] ?? 0);
                        $slotsCheckedIn = (int)($row['room_slots_checked_in'] ?? 0);
                        $slotsCheckedOut = (int)($row['room_slots_checked_out'] ?? 0);
                        $fullyCheckedIn = ($slotsTotal > 0 && $slotsCheckedIn >= $slotsTotal);
                        $partlyCheckedIn = ($slotsCheckedIn > 0 && !$fullyCheckedIn);
                        $fullyCheckedOut = ($slotsTotal > 0 && $slotsCheckedOut >= $slotsTotal);
                        $partlyCheckedOut = ($slotsCheckedOut > 0 && !$fullyCheckedOut);
                        $isNoShow = fd_is_no_show_order($row);
                        $isLateArrival = fd_is_late_arrival_order($row);
                        $arrivalDate = !empty($row['check_in']) ? date('Y-m-d', strtotime((string)$row['check_in'])) : '';
                        $departureDate = !empty($row['check_out']) ? date('Y-m-d', strtotime((string)$row['check_out'])) : '';
                        $canMarkNoShow = ($mode === 'arrival' && !$isNoShow && !$fullyCheckedIn && !$partlyCheckedIn && !$fullyCheckedOut && $arrivalDate <= date('Y-m-d'));
                        $canLateArrival = ($mode === 'arrival' && !$isNoShow && !$fullyCheckedIn && !$partlyCheckedIn && !$fullyCheckedOut);
                        $canExtendStay = (!$isNoShow && !$fullyCheckedOut && $departureDate !== '');

                        if ($isNoShow) {
                            $statusHtml = '<span class="badge text-bg-danger">No Show</span>';
                            $releaseLabel = !empty($row['no_show_inventory_released']) ? ' • inventory released' : '';
                            $policyActionLabel = str_replace('_', ' ', (string)($row['no_show_policy_action'] ?? ''));
                            $policyCharge = (float)($row['no_show_policy_charge_amount'] ?? ($row['no_show_policy_charge'] ?? 0));
                            $policyAdjustment = (float)($row['no_show_adjustment_amount_live'] ?? ($row['no_show_adjustment_amount'] ?? 0));
                            $statusDetail = 'Policy: ' . ($policyActionLabel !== '' ? ucwords($policyActionLabel) : 'No Show')
                                . ' • Waived: ' . money_fmt($policyAdjustment)
                                . ' • Charge: ' . money_fmt($policyCharge)
                                . $releaseLabel;
                        } elseif ($isLateArrival) {
                            $statusHtml = '<span class="badge text-bg-info text-dark">Expected Late Arrival</span>';
                            $statusDetail = 'Room held until: ' . trim((string)($row['room_hold_until'] ?? 'not set'));
                        } elseif ($fullyCheckedOut) {
                            $statusHtml = '<span class="badge text-bg-success">Checked Out</span>';
                            $statusDetail = 'Checked out: ' . $slotsCheckedOut . '/' . $slotsTotal;
                        } elseif ($partlyCheckedOut) {
                            $statusHtml = '<span class="badge text-bg-warning text-dark">Partial Check-Out</span>';
                            $statusDetail = 'Checked out: ' . $slotsCheckedOut . '/' . $slotsTotal;
                        } elseif ($fullyCheckedIn) {
                            $statusHtml = '<span class="badge text-bg-primary">In House</span>';
                            $statusDetail = 'Checked in: ' . $slotsCheckedIn . '/' . $slotsTotal;
                        } elseif ($partlyCheckedIn) {
                            $statusHtml = '<span class="badge text-bg-warning text-dark">Partial Check-In</span>';
                            $statusDetail = 'Checked in: ' . $slotsCheckedIn . '/' . $slotsTotal;
                        } else {
                            $statusHtml = '<span class="badge text-bg-secondary">Pending</span>';
                            $statusDetail = 'Checked in: 0/' . $slotsTotal;
                        }

                        $showCheckIn = ($slotsTotal > 0 && !$isNoShow && $slotsCheckedIn <= 0 && $slotsCheckedOut <= 0);
                        $showCheckOut = ($slotsTotal > 0 && !$isNoShow && $slotsCheckedIn > 0 && $slotsCheckedOut <= 0);
                        $safeMode = preg_replace('/[^a-z0-9_]+/i', '_', $mode);
                        $modalId = 'noShowModal' . (int)$row['id'] . '_' . $safeMode;
                        $lateModalId = 'lateArrivalModal' . (int)$row['id'] . '_' . $safeMode;
                        $extendModalId = 'extendStayModal' . (int)$row['id'] . '_' . $safeMode;
                        $holdModalId = 'holdRoomModal' . (int)$row['id'] . '_' . $safeMode;
                        ?>
                        <tr class="<?php echo $isNoShow ? 'table-danger-subtle' : ''; ?>">
                            <td><div class="data-stack"><strong><?php echo h($row['order_no'] ?: ('#' . $row['id'])); ?></strong><small>Booking ID <?php echo (int)$row['id']; ?></small></div></td>
                            <td><?php echo h($propertyLabel !== '' ? $propertyLabel : 'Not set'); ?></td>
                            <td><?php echo h($guestName); ?></td>
                            <td><?php echo h($agentName); ?></td>
                            <td><div class="data-stack"><strong><?php echo h($row['check_in']); ?></strong><small>to <?php echo h($row['check_out']); ?></small></div></td>
                            <td><?php render_room_mix_badges($row); ?><div class="small text-muted mt-1"><?php echo (int)($row['totalrooms'] ?? 1); ?> room(s) • <?php echo (int)($row['adults'] ?? 0); ?>A / <?php echo (int)($row['kids'] ?? 0); ?>C</div></td>
                            <td><?php echo $statusHtml; ?><div class="small text-muted mt-1"><?php echo h($statusDetail); ?></div></td>
                            <td class="text-end"><span class="badge text-bg-<?php echo abs($balance) > 0.00001 ? 'warning' : 'success'; ?> px-3 py-2"><?php echo money_fmt($balance); ?></span></td>
                            <td class="text-end">
                                <div class="d-inline-flex gap-2 flex-wrap justify-content-end">
                                    <?php if ($showCheckIn): ?><a href="/rooms/modules/payments/checkin.php?order_id=<?php echo (int)$row['id']; ?>" class="btn btn-sm btn-success"><i class="bi bi-door-open me-1"></i>Check In</a><?php endif; ?>
                                    <?php if ($showCheckOut): ?><a href="/rooms/modules/payments/check_out.php?order_id=<?php echo (int)$row['id']; ?>" class="btn btn-sm btn-danger"><i class="bi bi-door-closed me-1"></i>Check Out</a><?php endif; ?>
                                    <?php if ($canMarkNoShow): ?>
                                        <button type="button" class="btn btn-sm btn-outline-danger" data-bs-toggle="modal" data-bs-target="#<?php echo $modalId; ?>"><i class="bi bi-person-x me-1"></i>No Show</button>
                                    <?php endif; ?>
                                    <?php if ($canLateArrival): ?>
                                        <button type="button" class="btn btn-sm btn-outline-info" data-bs-toggle="modal" data-bs-target="#<?php echo $lateModalId; ?>"><i class="bi bi-clock-history me-1"></i>Late Arrival</button>
                                        <button type="button" class="btn btn-sm btn-outline-dark" data-bs-toggle="modal" data-bs-target="#<?php echo $holdModalId; ?>"><i class="bi bi-shield-check me-1"></i>Hold Room</button>
                                    <?php endif; ?>
                                    <?php if ($canExtendStay): ?>
                                        <button type="button" class="btn btn-sm btn-outline-success" data-bs-toggle="modal" data-bs-target="#<?php echo $extendModalId; ?>"><i class="bi bi-calendar-plus me-1"></i>Extend</button>
                                    <?php endif; ?>
                                    <a href="/rooms/modules/payments/payments.php?order_id=<?php echo (int)$row['id']; ?>" class="btn btn-sm btn-outline-primary"><i class="bi bi-cash-stack me-1"></i>Payments</a>
                                    <a href="/rooms/modules/payments/folio.php?q=<?php echo urlencode((string)($row['order_no'] ?: $row['id'])); ?>" class="btn btn-sm btn-outline-secondary"><i class="bi bi-receipt me-1"></i>Folios</a>
                                    <a href="/rooms/modules/reservations/reservation_view.php?order_id=<?php echo (int)$row['id']; ?>" class="btn btn-sm btn-light"><i class="bi bi-eye me-1"></i>View</a>
                                </div>

                                <?php if ($canMarkNoShow): ?>
                                    <div class="modal fade" id="<?php echo $modalId; ?>" tabindex="-1" aria-hidden="true">
                                        <div class="modal-dialog modal-dialog-centered">
                                            <div class="modal-content border-0 shadow-lg text-start">
                                                <div class="modal-header bg-danger text-white">
                                                    <h5 class="modal-title"><i class="bi bi-person-x me-2"></i>Mark No Show</h5>
                                                    <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
                                                </div>
                                                <form method="post" class="js-no-show-form" data-current-balance="<?php echo h(number_format(max(0, (float)($row['folio_total_balance'] ?? 0)), 2, '.', '')); ?>" onsubmit="return confirm('Mark this booking as No Show?');">
                                                    <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                                    <input type="hidden" name="action" value="mark_no_show">
                                                    <input type="hidden" name="order_id" value="<?php echo (int)$row['id']; ?>">
                                                    <div class="modal-body">
                                                        <div class="alert alert-warning small">
                                                            This will remove the booking from normal check-in flow and flag it for Night Audit No Show review.
                                                        </div>
                                                        <div class="mb-3">
                                                            <label class="form-label">Reason / Notes</label>
                                                            <input type="text" name="reason" class="form-control" value="Guest did not arrive.">
                                                        </div>
                                                        <div class="form-check mb-3">
                                                            <input class="form-check-input" type="checkbox" name="release_inventory" value="1" id="releaseInventory<?php echo (int)$row['id']; ?>" checked>
                                                            <label class="form-check-label" for="releaseInventory<?php echo (int)$row['id']; ?>">Release allocated rooms/inventory for resale</label>
                                                            <div class="form-text">Untick only if management wants to keep the room blocked.</div>
                                                        </div>
                                                        <div class="border rounded-3 p-3 mb-3 bg-light">
                                                            <div class="fw-bold mb-2">No Show Accounting</div>
                                                            <div class="small text-muted mb-2">
                                                                Current folio balance: <strong><?php echo money_fmt($row['folio_total_balance'] ?? 0); ?></strong>
                                                            </div>
                                                            <div class="form-check mb-2">
                                                                <input class="form-check-input no-show-policy-radio" type="radio" name="policy_action" value="keep_balance" id="policyKeep<?php echo (int)$row['id']; ?>" checked>
                                                                <label class="form-check-label" for="policyKeep<?php echo (int)$row['id']; ?>">
                                                                    Charge existing folio as is
                                                                </label>
                                                                <div class="form-text">Existing folio balance remains payable. The charge field shows the current balance for reference only; nothing is reposted.</div>
                                                            </div>
                                                            <div class="form-check mb-2">
                                                                <input class="form-check-input no-show-policy-radio" type="radio" name="policy_action" value="waive_all" id="policyWaive<?php echo (int)$row['id']; ?>">
                                                                <label class="form-check-label" for="policyWaive<?php echo (int)$row['id']; ?>">
                                                                    Waive entire folio balance
                                                                </label>
                                                                <div class="form-text">Posts a credit adjustment for the current balance. Final balance becomes zero unless there are later postings.</div>
                                                            </div>
                                                            <div class="form-check mb-2">
                                                                <input class="form-check-input no-show-policy-radio" type="radio" name="policy_action" value="waive_and_charge_policy" id="policyCharge<?php echo (int)$row['id']; ?>">
                                                                <label class="form-check-label" for="policyCharge<?php echo (int)$row['id']; ?>">
                                                                    Post cancellation adjustment and policy charge
                                                                </label>
                                                                <div class="form-text">Use this for policies like first night only, 50% charge, custom no-show charge, or partial waiver.</div>
                                                            </div>
                                                            <div class="row g-2 mt-1">
                                                                <div class="col-md-6">
                                                                    <label class="form-label">Cancellation Adjustment / Waiver</label>
                                                                    <input type="number" step="0.01" min="0" name="policy_adjustment_amount" class="form-control js-policy-adjustment" value="0.00" readonly>
                                                                    <div class="form-text">Credit amount to remove from the existing folio balance. Locked unless policy charge mode is selected.</div>
                                                                </div>
                                                                <div class="col-md-6">
                                                                    <label class="form-label">No Show Policy Charge</label>
                                                                    <input type="number" step="0.01" min="0" name="policy_charge_amount" class="form-control js-policy-charge" value="<?php echo h(number_format(max(0, (float)($row['folio_total_balance'] ?? 0)), 2, '.', '')); ?>" readonly>
                                                                    <div class="form-text">For Charge Existing Balance this shows the current balance only. For Waive Entire Balance it stays 0.00.</div>
                                                                </div>
                                                            </div>
                                                            <div class="alert alert-light border small mt-3 mb-0">
                                                                Example: current balance 1,300, adjustment 1,050, policy charge 250 = final balance 500.
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <div class="modal-footer">
                                                        <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
                                                        <button type="submit" class="btn btn-danger"><i class="bi bi-person-x me-1"></i>Confirm No Show</button>
                                                    </div>
                                                </form>
                                            </div>
                                        </div>
                                    </div>
                                <?php endif; ?>

                                <?php if ($canLateArrival): ?>
                                    <div class="modal fade" id="<?php echo $lateModalId; ?>" tabindex="-1" aria-hidden="true">
                                        <div class="modal-dialog modal-dialog-centered">
                                            <div class="modal-content border-0 shadow-lg text-start">
                                                <div class="modal-header bg-info text-dark">
                                                    <h5 class="modal-title"><i class="bi bi-clock-history me-2"></i>Expected Late Arrival</h5>
                                                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                                                </div>
                                                <form method="post" onsubmit="return confirm('Save late arrival and check availability?');">
                                                    <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                                    <input type="hidden" name="action" value="late_arrival">
                                                    <input type="hidden" name="order_id" value="<?php echo (int)$row['id']; ?>">
                                                    <div class="modal-body">
                                                        <div class="alert alert-info small">Use this when the guest is still coming, but the arrival date changes. The system checks availability before saving.</div>
                                                        <div class="row g-2">
                                                            <div class="col-md-6">
                                                                <label class="form-label">New Arrival Date</label>
                                                                <input type="date" name="new_check_in" class="form-control" value="<?php echo h($arrivalDate !== '' ? date('Y-m-d', strtotime($arrivalDate . ' +1 day')) : date('Y-m-d')); ?>" required>
                                                            </div>
                                                            <div class="col-md-6">
                                                                <label class="form-label">Check-Out Date</label>
                                                                <input type="date" name="new_check_out" class="form-control" value="<?php echo h($departureDate); ?>" required>
                                                            </div>
                                                        </div>
                                                        <div class="form-check mt-3">
                                                            <input class="form-check-input" type="checkbox" name="hold_room" value="1" id="holdLate<?php echo (int)$row['id']; ?>" checked>
                                                            <label class="form-check-label" for="holdLate<?php echo (int)$row['id']; ?>">Hold room until new arrival date</label>
                                                        </div>
                                                        <div class="mt-3">
                                                            <label class="form-label">Reason / Notes</label>
                                                            <input type="text" name="reason" class="form-control" value="Guest expected to arrive late.">
                                                        </div>
                                                    </div>
                                                    <div class="modal-footer"><button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button><button type="submit" class="btn btn-info"><i class="bi bi-save me-1"></i>Save Late Arrival</button></div>
                                                </form>
                                            </div>
                                        </div>
                                    </div>

                                    <div class="modal fade" id="<?php echo $holdModalId; ?>" tabindex="-1" aria-hidden="true">
                                        <div class="modal-dialog modal-dialog-centered">
                                            <div class="modal-content border-0 shadow-lg text-start">
                                                <div class="modal-header bg-dark text-white">
                                                    <h5 class="modal-title"><i class="bi bi-shield-check me-2"></i>Hold Room</h5>
                                                    <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
                                                </div>
                                                <form method="post" onsubmit="return confirm('Hold this room for the delayed guest?');">
                                                    <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                                    <input type="hidden" name="action" value="hold_room">
                                                    <input type="hidden" name="order_id" value="<?php echo (int)$row['id']; ?>">
                                                    <div class="modal-body">
                                                        <div class="alert alert-warning small">Use this when the guest is delayed but management wants to keep the original room blocked.</div>
                                                        <label class="form-label">Hold Until</label>
                                                        <input type="datetime-local" name="hold_until" class="form-control" value="<?php echo h(date('Y-m-d\T23:59', strtotime($arrivalDate ?: date('Y-m-d')))); ?>">
                                                        <label class="form-label mt-3">Reason / Notes</label>
                                                        <input type="text" name="reason" class="form-control" value="Room held for delayed guest.">
                                                    </div>
                                                    <div class="modal-footer"><button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button><button type="submit" class="btn btn-dark"><i class="bi bi-shield-check me-1"></i>Hold Room</button></div>
                                                </form>
                                            </div>
                                        </div>
                                    </div>
                                <?php endif; ?>

                                <?php if ($canExtendStay): ?>
                                    <div class="modal fade" id="<?php echo $extendModalId; ?>" tabindex="-1" aria-hidden="true">
                                        <div class="modal-dialog modal-dialog-centered">
                                            <div class="modal-content border-0 shadow-lg text-start">
                                                <div class="modal-header bg-success text-white">
                                                    <h5 class="modal-title"><i class="bi bi-calendar-plus me-2"></i>Extend Stay</h5>
                                                    <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
                                                </div>
                                                <form method="post" onsubmit="return confirm('Check availability and extend this stay?');">
                                                    <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                                    <input type="hidden" name="action" value="extend_stay">
                                                    <input type="hidden" name="order_id" value="<?php echo (int)$row['id']; ?>">
                                                    <div class="modal-body">
                                                        <div class="alert alert-success small">The system checks the allocated room / room type availability before extending the booking.</div>
                                                        <label class="form-label">Current Check-Out</label>
                                                        <input type="date" class="form-control mb-2" value="<?php echo h($departureDate); ?>" readonly>
                                                        <label class="form-label">New Check-Out</label>
                                                        <input type="date" name="new_check_out" class="form-control" value="<?php echo h($departureDate !== '' ? date('Y-m-d', strtotime($departureDate . ' +1 day')) : date('Y-m-d', strtotime('+1 day'))); ?>" required>
                                                    </div>
                                                    <div class="modal-footer"><button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button><button type="submit" class="btn btn-success"><i class="bi bi-calendar-plus me-1"></i>Check & Extend</button></div>
                                                </form>
                                            </div>
                                        </div>
                                    </div>
                                <?php endif; ?>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                <?php endif; ?>
            </tbody>
        </table>
    </div>
    <?php
}
?>

<div class="card panel-card mb-4">
    <div class="card-header"><h5 class="mb-0">Arrivals Today</h5></div>
    <div class="card-body p-0"><?php render_booking_table($arrivals, 'arrival'); ?></div>
</div>

<div class="card panel-card mb-4">
    <div class="card-header"><h5 class="mb-0">In House</h5></div>
    <div class="card-body p-0"><?php render_booking_table($inHouse, 'inhouse'); ?></div>
</div>

<div class="card panel-card">
    <div class="card-header"><h5 class="mb-0">Departures Today</h5></div>
    <div class="card-body p-0"><?php render_booking_table($departures, 'departure'); ?></div>
</div>

<style>
.frontdesk-compact-hero{padding:14px 18px!important;border-radius:18px!important;margin-bottom:10px!important;}
.frontdesk-compact-hero .row{--bs-gutter-y:.5rem!important;}
.frontdesk-compact-hero .hero-title{font-size:1.35rem!important;line-height:1.15!important;margin-top:.2rem!important;}
.frontdesk-compact-hero .hero-subtitle{font-size:.86rem!important;margin-top:.25rem!important;line-height:1.25!important;}
.frontdesk-compact-hero .page-eyebrow{font-size:.76rem!important;}
.frontdesk-compact-hero .btn{padding:.36rem .75rem!important;font-size:.84rem!important;border-radius:12px!important;}
.frontdesk-metrics .metric-card{padding:10px 12px!important;border-radius:16px!important;min-height:62px!important;}
.frontdesk-metrics .metric-icon{width:36px!important;height:36px!important;border-radius:12px!important;font-size:15px!important;}
.frontdesk-metrics .metric-label{font-size:.75rem!important;margin-bottom:0!important;}
.frontdesk-metrics .metric-value{font-size:1.05rem!important;line-height:1.1!important;}
.frontdesk-filter-card .card-body{padding:.75rem!important;}
.frontdesk-filter-card .form-label{font-size:.76rem!important;font-weight:700!important;margin-bottom:.2rem!important;}
.frontdesk-filter-card .form-control,.frontdesk-filter-card .form-select{min-height:34px!important;padding:.35rem .65rem!important;font-size:.86rem!important;border-radius:10px!important;}
.frontdesk-filter-card .btn{min-height:34px!important;padding:.35rem .65rem!important;border-radius:10px!important;}
.panel-card{border-radius:18px!important;overflow:hidden!important;}
.panel-card>.card-header{padding:.6rem .9rem!important;background:#fff!important;border-bottom:1px solid #edf2f7!important;}
.panel-card>.card-header h5{font-size:.98rem!important;font-weight:800!important;}
.frontdesk-table-wrap{max-height:430px;overflow:auto;border-radius:0 0 18px 18px;}
.frontdesk-table thead th{position:sticky;top:0;z-index:20;background:#f8fafc!important;box-shadow:0 1px 0 #e5e7eb;font-size:.76rem!important;white-space:nowrap;}
.frontdesk-table tbody td{font-size:.84rem!important;vertical-align:middle!important;}
.frontdesk-table .btn{padding:.28rem .52rem!important;font-size:.76rem!important;border-radius:9px!important;}
.frontdesk-table .badge{font-size:.72rem!important;}
.frontdesk-table .data-stack strong{font-size:.84rem!important;}
.frontdesk-table .data-stack small{font-size:.72rem!important;}
.frontdesk-table .small{font-size:.72rem!important;}
.frontdesk-table td,.frontdesk-table th{padding:.55rem .65rem!important;}
.table-danger-subtle td{background:#fff5f5!important;}
.frontdesk-table .modal .form-label{font-weight:800;font-size:.78rem}.frontdesk-table .modal .alert{border-radius:12px}.frontdesk-table .modal .btn{border-radius:10px!important}
@media (max-width: 991.98px){.frontdesk-table-wrap{max-height:70vh;}}
</style>

<script>
document.addEventListener('DOMContentLoaded', function () {
    function money2(value) {
        var n = parseFloat(value || '0');
        if (!isFinite(n) || n < 0) n = 0;
        return n.toFixed(2);
    }

    document.querySelectorAll('.js-no-show-form').forEach(function (form) {
        var balance = parseFloat(form.getAttribute('data-current-balance') || '0');
        if (!isFinite(balance) || balance < 0) balance = 0;

        var adjustment = form.querySelector('.js-policy-adjustment');
        var charge = form.querySelector('.js-policy-charge');
        var radios = form.querySelectorAll('input[name="policy_action"]');

        function syncPolicyFields() {
            var selected = form.querySelector('input[name="policy_action"]:checked');
            var action = selected ? selected.value : 'keep_balance';

            if (!adjustment || !charge) return;

            if (action === 'keep_balance') {
                adjustment.value = '0.00';
                adjustment.readOnly = true;
                adjustment.classList.add('bg-light', 'text-muted');

                charge.value = money2(balance);
                charge.readOnly = true;
                charge.classList.add('bg-light', 'text-muted');
            } else if (action === 'waive_all') {
                adjustment.value = money2(balance);
                adjustment.readOnly = true;
                adjustment.classList.add('bg-light', 'text-muted');

                charge.value = '0.00';
                charge.readOnly = true;
                charge.classList.add('bg-light', 'text-muted');
            } else {
                adjustment.value = money2(balance);
                adjustment.readOnly = false;
                adjustment.classList.remove('bg-light', 'text-muted');

                charge.value = '0.00';
                charge.readOnly = false;
                charge.classList.remove('bg-light', 'text-muted');
            }
        }

        radios.forEach(function (radio) {
            radio.addEventListener('change', syncPolicyFields);
        });
        syncPolicyFields();
    });
});
</script>

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