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

$pageTitle = 'Extras / Charges';

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('extras_db_column_exists')) {
    function extras_db_column_exists(PDO $pdo, string $tableName, string $columnName): bool
    {
        static $cache = [];
        $key = $tableName . '.' . $columnName;
        if (array_key_exists($key, $cache)) return $cache[$key];
        try {
            $stmt = $pdo->prepare("
                SELECT COUNT(*)
                FROM INFORMATION_SCHEMA.COLUMNS
                WHERE TABLE_SCHEMA = DATABASE()
                  AND TABLE_NAME = :table_name
                  AND COLUMN_NAME = :column_name
            ");
            $stmt->execute([':table_name' => $tableName, ':column_name' => $columnName]);
            return $cache[$key] = ((int)$stmt->fetchColumn() > 0);
        } catch (Throwable $e) {
            return $cache[$key] = false;
        }
    }
}

if (!function_exists('extras_try_add_column')) {
    function extras_try_add_column(PDO $pdo, string $tableName, string $columnName, string $definition): void
    {
        if (extras_db_column_exists($pdo, $tableName, $columnName)) return;
        try {
            $pdo->exec("ALTER TABLE `" . str_replace('`', '``', $tableName) . "` ADD COLUMN " . $definition);
        } catch (Throwable $e) {
            // Keep saving operational even if the database user cannot ALTER.
        }
    }
}


if (!function_exists('extras_current_user_id')) {
    function extras_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('ensure_extra_charge_audit_table')) {
    function ensure_extra_charge_audit_table(PDO $pdo): void
    {
        $pdo->exec("
            CREATE TABLE IF NOT EXISTS extra_charge_audit_log (
                id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
                order_id INT UNSIGNED NOT NULL,
                folio_id INT UNSIGNED NOT NULL,
                folio_transaction_id INT UNSIGNED NULL,
                service_id INT UNSIGNED NOT NULL,
                action_type VARCHAR(80) NOT NULL DEFAULT 'manual_unit_price_override',
                standard_unit_price DECIMAL(14,2) NOT NULL DEFAULT 0.00,
                override_unit_price DECIMAL(14,2) NOT NULL DEFAULT 0.00,
                quantity DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
                standard_amount DECIMAL(14,2) NOT NULL DEFAULT 0.00,
                override_amount DECIMAL(14,2) NOT NULL DEFAULT 0.00,
                reason VARCHAR(255) NULL,
                payload_json TEXT NULL,
                action_by INT UNSIGNED NULL,
                action_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_extra_audit_order (order_id),
                INDEX idx_extra_audit_folio (folio_id),
                INDEX idx_extra_audit_tx (folio_transaction_id),
                INDEX idx_extra_audit_service (service_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        ");
    }
}




if (!function_exists('ensure_extra_charge_item_link_table')) {
    function ensure_extra_charge_item_link_table(PDO $pdo): void
    {
        $pdo->exec("
            CREATE TABLE IF NOT EXISTS extra_charge_items (
                id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
                property_id INT UNSIGNED NULL,
                order_id INT UNSIGNED NOT NULL,
                folio_id INT UNSIGNED NOT NULL,
                folio_transaction_id BIGINT UNSIGNED NOT NULL,
                line_no INT UNSIGNED NOT NULL DEFAULT 1,
                category_id INT UNSIGNED NULL,
                category_name VARCHAR(180) NULL,
                source_table VARCHAR(80) NOT NULL,
                source_id INT UNSIGNED NOT NULL,
                item_type VARCHAR(80) NULL,
                item_name VARCHAR(255) NOT NULL,
                item_code VARCHAR(100) NULL,
                qty DECIMAL(14,4) NOT NULL DEFAULT 1.0000,
                standard_unit_price DECIMAL(14,2) NOT NULL DEFAULT 0.00,
                unit_price DECIMAL(14,2) NOT NULL DEFAULT 0.00,
                standard_amount DECIMAL(14,2) NOT NULL DEFAULT 0.00,
                line_total DECIMAL(14,2) NOT NULL DEFAULT 0.00,
                is_manual_override TINYINT(1) NOT NULL DEFAULT 0,
                description VARCHAR(255) NULL,
                source_payload_json TEXT NULL,
                created_by INT UNSIGNED NULL,
                created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_extra_items_tx (folio_transaction_id),
                INDEX idx_extra_items_order (order_id),
                INDEX idx_extra_items_folio (folio_id),
                INDEX idx_extra_items_source (source_table, source_id),
                INDEX idx_extra_items_category (category_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        ");


        // Older installations may already have extra_charge_items without the
        // newer linkage columns. Add what we need without breaking older data.
        extras_try_add_column($pdo, 'extra_charge_items', 'property_id', '`property_id` INT UNSIGNED NULL AFTER `id`');
        extras_try_add_column($pdo, 'extra_charge_items', 'order_id', '`order_id` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `property_id`');
        extras_try_add_column($pdo, 'extra_charge_items', 'folio_id', '`folio_id` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `order_id`');
        extras_try_add_column($pdo, 'extra_charge_items', 'folio_transaction_id', '`folio_transaction_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER `folio_id`');
        extras_try_add_column($pdo, 'extra_charge_items', 'line_no', '`line_no` INT UNSIGNED NOT NULL DEFAULT 1 AFTER `folio_transaction_id`');
        extras_try_add_column($pdo, 'extra_charge_items', 'category_id', '`category_id` INT UNSIGNED NULL AFTER `line_no`');
        extras_try_add_column($pdo, 'extra_charge_items', 'category_name', '`category_name` VARCHAR(180) NULL AFTER `category_id`');
        extras_try_add_column($pdo, 'extra_charge_items', 'source_table', "`source_table` VARCHAR(80) NOT NULL DEFAULT 'tblservices' AFTER `category_name`");
        extras_try_add_column($pdo, 'extra_charge_items', 'source_id', '`source_id` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `source_table`');
        extras_try_add_column($pdo, 'extra_charge_items', 'item_type', '`item_type` VARCHAR(80) NULL AFTER `source_id`');
        extras_try_add_column($pdo, 'extra_charge_items', 'item_name', "`item_name` VARCHAR(255) NOT NULL DEFAULT 'Extra Item' AFTER `item_type`");
        extras_try_add_column($pdo, 'extra_charge_items', 'item_code', '`item_code` VARCHAR(100) NULL AFTER `item_name`');
        extras_try_add_column($pdo, 'extra_charge_items', 'qty', '`qty` DECIMAL(14,4) NOT NULL DEFAULT 1.0000 AFTER `item_code`');
        extras_try_add_column($pdo, 'extra_charge_items', 'standard_unit_price', '`standard_unit_price` DECIMAL(14,2) NOT NULL DEFAULT 0.00 AFTER `qty`');
        extras_try_add_column($pdo, 'extra_charge_items', 'unit_price', '`unit_price` DECIMAL(14,2) NOT NULL DEFAULT 0.00 AFTER `standard_unit_price`');
        extras_try_add_column($pdo, 'extra_charge_items', 'standard_amount', '`standard_amount` DECIMAL(14,2) NOT NULL DEFAULT 0.00 AFTER `unit_price`');
        extras_try_add_column($pdo, 'extra_charge_items', 'line_total', '`line_total` DECIMAL(14,2) NOT NULL DEFAULT 0.00 AFTER `standard_amount`');
        extras_try_add_column($pdo, 'extra_charge_items', 'is_manual_override', '`is_manual_override` TINYINT(1) NOT NULL DEFAULT 0 AFTER `line_total`');
        extras_try_add_column($pdo, 'extra_charge_items', 'description', '`description` VARCHAR(255) NULL AFTER `is_manual_override`');
        extras_try_add_column($pdo, 'extra_charge_items', 'source_payload_json', '`source_payload_json` TEXT NULL AFTER `description`');
        extras_try_add_column($pdo, 'extra_charge_items', 'created_by', '`created_by` INT UNSIGNED NULL AFTER `source_payload_json`');
        extras_try_add_column($pdo, 'extra_charge_items', 'created_at', '`created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP AFTER `created_by`');
    }
}


if (!function_exists('extras_ensure_service_related_schema')) {
    function extras_ensure_service_related_schema(PDO $pdo): void
    {
        if (!extras_table_exists($pdo, 'tblservices')) return;

        extras_try_add_column($pdo, 'tblservices', 'requires_extra', '`requires_extra` TINYINT(1) NOT NULL DEFAULT 0 AFTER `price_per_person`');
        extras_try_add_column($pdo, 'tblservices', 'related_service_id_1', '`related_service_id_1` INT UNSIGNED NULL AFTER `requires_extra`');
        extras_try_add_column($pdo, 'tblservices', 'related_service_id_2', '`related_service_id_2` INT UNSIGNED NULL AFTER `related_service_id_1`');
    }
}

if (!function_exists('extras_folio_label')) {
    function extras_folio_label(array $folio): string
    {
        $type = strtolower(trim((string)($folio['folio_type'] ?? '')));
        $name = trim((string)($folio['folio_name'] ?? ''));
        $roomNo = trim((string)($folio['room_no'] ?? ''));
        $roomType = trim((string)($folio['room_type'] ?? ''));
        $occupantName = trim((string)($folio['occupant_name'] ?? ''));

        if ($type === 'master') {
            return 'Master Folio' . ($name !== '' ? ' - ' . $name : '');
        }
        if ($type === 'room') {
            $label = 'Room ' . ($roomNo !== '' ? $roomNo : 'N/A');
            if ($roomType !== '') $label .= ' • ' . $roomType;
            if ($name !== '') $label .= ' - ' . $name;
            return $label;
        }
        if ($type === 'guest') {
            $label = 'Guest Folio';
            if ($occupantName !== '') $label .= ' • ' . $occupantName;
            elseif ($name !== '') $label .= ' - ' . $name;
            if ($roomNo !== '') $label .= ' / Room ' . $roomNo;
            return $label;
        }

        return ($name !== '' ? $name : ('Folio #' . (int)($folio['id'] ?? 0)));
    }
}

if (!function_exists('move_folio_transaction_to_folio')) {
    function move_folio_transaction_to_folio(PDO $pdo, int $orderId, int $transactionId, int $targetFolioId, string $reason = ''): void
    {
        if ($orderId <= 0 || $transactionId <= 0 || $targetFolioId <= 0) {
            throw new RuntimeException('Invalid move transaction request.');
        }

        $stmt = $pdo->prepare("\n            SELECT\n                ft.*,\n                f.order_id AS current_order_id,\n                f.folio_name AS current_folio_name,\n                f.folio_type AS current_folio_type\n            FROM folio_transactions ft\n            INNER JOIN folios f ON f.id = ft.folio_id\n            WHERE ft.id = :transaction_id\n              AND ft.order_id = :order_id\n            LIMIT 1\n        ");
        $stmt->execute([
            ':transaction_id' => $transactionId,
            ':order_id' => $orderId,
        ]);
        $tx = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$tx) {
            throw new RuntimeException('Folio transaction was not found for this booking.');
        }

        $oldFolioId = (int)$tx['folio_id'];
        if ($oldFolioId === $targetFolioId) {
            throw new RuntimeException('The transaction is already on the selected folio.');
        }

        $stmt = $pdo->prepare("\n            SELECT *\n            FROM folios\n            WHERE id = :target_folio_id\n              AND order_id = :order_id\n              AND COALESCE(status, 1) = 1\n            LIMIT 1\n        ");
        $stmt->execute([
            ':target_folio_id' => $targetFolioId,
            ':order_id' => $orderId,
        ]);
        $targetFolio = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$targetFolio) {
            throw new RuntimeException('Destination folio was not found or does not belong to this booking.');
        }

        $pdo->prepare("\n            UPDATE folio_transactions\n            SET folio_id = :target_folio_id\n            WHERE id = :transaction_id\n              AND order_id = :order_id\n        ")->execute([
            ':target_folio_id' => $targetFolioId,
            ':transaction_id' => $transactionId,
            ':order_id' => $orderId,
        ]);

        // Keep extra override audit rows attached to the same folio as the moved transaction.
        try {
            ensure_extra_charge_audit_table($pdo);
            $pdo->prepare("\n                UPDATE extra_charge_audit_log\n                SET folio_id = :target_folio_id\n                WHERE folio_transaction_id = :transaction_id\n                  AND order_id = :order_id\n            ")->execute([
                ':target_folio_id' => $targetFolioId,
                ':transaction_id' => $transactionId,
                ':order_id' => $orderId,
            ]);

            ensure_extra_charge_item_link_table($pdo);
            $pdo->prepare("\n                UPDATE extra_charge_items\n                SET folio_id = :target_folio_id\n                WHERE folio_transaction_id = :transaction_id\n                  AND order_id = :order_id\n            ")->execute([
                ':target_folio_id' => $targetFolioId,
                ':transaction_id' => $transactionId,
                ':order_id' => $orderId,
            ]);

            $payload = json_encode([
                'transaction_id' => $transactionId,
                'from_folio_id' => $oldFolioId,
                'to_folio_id' => $targetFolioId,
                'from_folio' => trim((string)($tx['current_folio_name'] ?? '')),
                'to_folio' => trim((string)($targetFolio['folio_name'] ?? '')),
                'tx_type' => $tx['tx_type'] ?? '',
                'description' => $tx['description'] ?? '',
                'debit' => (float)($tx['debit'] ?? 0),
                'credit' => (float)($tx['credit'] ?? 0),
                'reason' => $reason,
            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

            $stmtAudit = $pdo->prepare("\n                INSERT INTO extra_charge_audit_log\n                (order_id, folio_id, folio_transaction_id, service_id, action_type,\n                 standard_unit_price, override_unit_price, quantity, standard_amount, override_amount,\n                 reason, payload_json, action_by)\n                VALUES\n                (:order_id, :folio_id, :folio_transaction_id, :service_id, 'move_folio_transaction',\n                 0, 0, 0, :standard_amount, :override_amount, :reason, :payload_json, :action_by)\n            ");
            $stmtAudit->execute([
                ':order_id' => $orderId,
                ':folio_id' => $targetFolioId,
                ':folio_transaction_id' => $transactionId,
                ':service_id' => (strtolower((string)($tx['source_table'] ?? '')) === 'tblservices') ? (int)($tx['source_id'] ?? 0) : 0,
                ':standard_amount' => round((float)($tx['debit'] ?? 0) - (float)($tx['credit'] ?? 0), 2),
                ':override_amount' => round((float)($tx['debit'] ?? 0) - (float)($tx['credit'] ?? 0), 2),
                ':reason' => $reason !== '' ? $reason : 'Moved to correct folio.',
                ':payload_json' => $payload,
                ':action_by' => extras_current_user_id(),
            ]);
        } catch (Throwable $auditError) {
            // Do not block the operational move because of an audit table issue.
        }

        recalculate_folio_totals($pdo, $oldFolioId);
        recalculate_folio_totals($pdo, $targetFolioId);
        sync_order_payment_status($pdo, $orderId);
    }
}

/* ===========================
   ENSURE MASTER FOLIO
=========================== */

/* ===========================
   EXTRAS PAID BY ANOTHER AGENT
=========================== */
if (!function_exists('ensure_booking_extra_agent_payers_table')) {
    function ensure_booking_extra_agent_payers_table(PDO $pdo): void
    {
        $pdo->exec("CREATE TABLE IF NOT EXISTS booking_extra_agent_payers (
            transaction_id BIGINT UNSIGNED NOT NULL,
            order_id BIGINT UNSIGNED NOT NULL,
            agent_id BIGINT UNSIGNED NOT NULL,
            folio_id BIGINT UNSIGNED NOT NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (transaction_id),
            KEY idx_beap_order (order_id),
            KEY idx_beap_agent (agent_id),
            KEY idx_beap_folio (folio_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

        // Compatibility with reservation_form.php / Booking Control Centre schemas.
        extras_try_add_column($pdo, 'booking_extra_agent_payers', 'folio_transaction_id',
            '`folio_transaction_id` BIGINT UNSIGNED NULL AFTER `transaction_id`');
        extras_try_add_column($pdo, 'booking_extra_agent_payers', 'payer_agent_id',
            '`payer_agent_id` BIGINT UNSIGNED NULL AFTER `agent_id`');
        extras_try_add_column($pdo, 'booking_extra_agent_payers', 'service_id',
            '`service_id` BIGINT UNSIGNED NULL AFTER `payer_agent_id`');

        // Keep old and new column names aligned for existing rows.
        try {
            if (extras_db_column_exists($pdo, 'booking_extra_agent_payers', 'folio_transaction_id')) {
                $pdo->exec("UPDATE booking_extra_agent_payers
                            SET folio_transaction_id = transaction_id
                            WHERE folio_transaction_id IS NULL OR folio_transaction_id = 0");
            }
            if (extras_db_column_exists($pdo, 'booking_extra_agent_payers', 'payer_agent_id')) {
                $pdo->exec("UPDATE booking_extra_agent_payers
                            SET payer_agent_id = agent_id
                            WHERE payer_agent_id IS NULL OR payer_agent_id = 0");
            }
        } catch (Throwable $e) {
            // Operational inserts below still support whichever columns exist.
        }
    }
}

if (!function_exists('extras_save_agent_payer_mapping')) {
    function extras_save_agent_payer_mapping(PDO $pdo, int $transactionId, int $orderId, int $agentId, int $folioId, int $serviceId = 0): void
    {
        ensure_booking_extra_agent_payers_table($pdo);

        // Remove an old mapping using every transaction-link column available.
        $deleteWhere = [];
        $deleteParams = [':delete_order_id' => $orderId];
        if (extras_db_column_exists($pdo, 'booking_extra_agent_payers', 'transaction_id')) {
            $deleteWhere[] = 'transaction_id = :delete_transaction_id';
            $deleteParams[':delete_transaction_id'] = $transactionId;
        }
        if (extras_db_column_exists($pdo, 'booking_extra_agent_payers', 'folio_transaction_id')) {
            $deleteWhere[] = 'folio_transaction_id = :delete_folio_transaction_id';
            $deleteParams[':delete_folio_transaction_id'] = $transactionId;
        }
        if ($deleteWhere) {
            $pdo->prepare('DELETE FROM booking_extra_agent_payers WHERE order_id = :delete_order_id AND (' . implode(' OR ', $deleteWhere) . ')')
                ->execute($deleteParams);
        }

        $columns = ['order_id', 'folio_id'];
        $values = [':insert_order_id', ':insert_folio_id'];
        $params = [
            ':insert_order_id' => $orderId,
            ':insert_folio_id' => $folioId,
        ];

        if (extras_db_column_exists($pdo, 'booking_extra_agent_payers', 'transaction_id')) {
            $columns[] = 'transaction_id';
            $values[] = ':insert_transaction_id';
            $params[':insert_transaction_id'] = $transactionId;
        }
        if (extras_db_column_exists($pdo, 'booking_extra_agent_payers', 'folio_transaction_id')) {
            $columns[] = 'folio_transaction_id';
            $values[] = ':insert_folio_transaction_id';
            $params[':insert_folio_transaction_id'] = $transactionId;
        }
        if (extras_db_column_exists($pdo, 'booking_extra_agent_payers', 'agent_id')) {
            $columns[] = 'agent_id';
            $values[] = ':insert_agent_id';
            $params[':insert_agent_id'] = $agentId;
        }
        if (extras_db_column_exists($pdo, 'booking_extra_agent_payers', 'payer_agent_id')) {
            $columns[] = 'payer_agent_id';
            $values[] = ':insert_payer_agent_id';
            $params[':insert_payer_agent_id'] = $agentId;
        }
        if (extras_db_column_exists($pdo, 'booking_extra_agent_payers', 'service_id')) {
            $columns[] = 'service_id';
            $values[] = ':insert_service_id';
            $params[':insert_service_id'] = $serviceId > 0 ? $serviceId : null;
        }

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

if (!function_exists('extras_agent_display_expression')) {
    function extras_agent_display_expression(PDO $pdo, string $alias = ''): string
    {
        $prefix = $alias !== '' ? preg_replace('/[^A-Za-z0-9_]/', '', $alias) . '.' : '';
        $parts = [];

        foreach (['company', 'Agent', 'RegisteredName', 'agent_name', 'agency_name', 'name', 'title'] as $column) {
            if (extras_column_exists($pdo, 'agents', $column)) {
                $safe = str_replace('`', '``', $column);
                $parts[] = "NULLIF(TRIM({$prefix}`{$safe}`), '')";
            }
        }

        if (extras_column_exists($pdo, 'agents', 'firstname') || extras_column_exists($pdo, 'agents', 'lastname')) {
            $first = extras_column_exists($pdo, 'agents', 'firstname') ? "COALESCE({$prefix}`firstname`, '')" : "''";
            $last  = extras_column_exists($pdo, 'agents', 'lastname') ? "COALESCE({$prefix}`lastname`, '')" : "''";
            $parts[] = "NULLIF(TRIM(CONCAT({$first}, ' ', {$last})), '')";
        }

        if (extras_column_exists($pdo, 'agents', 'email')) {
            $parts[] = "NULLIF(TRIM({$prefix}`email`), '')";
        }

        $parts[] = "CONCAT('Agent #', {$prefix}`id`)";
        return 'COALESCE(' . implode(', ', $parts) . ')';
    }
}

if (!function_exists('extras_agent_name')) {
    function extras_agent_name(PDO $pdo, int $agentId): string
    {
        if ($agentId <= 0) return '';
        $expr = extras_agent_display_expression($pdo);
        $stmt = $pdo->prepare("SELECT {$expr} FROM agents WHERE id = :id LIMIT 1");
        $stmt->execute([':id' => $agentId]);
        return trim((string)($stmt->fetchColumn() ?: ''));
    }
}

if (!function_exists('get_or_create_other_agent_folio')) {
    function get_or_create_other_agent_folio(PDO $pdo, int $orderId, int $propertyId, int $agentId): int
    {
        $agentName = extras_agent_name($pdo, $agentId);
        if ($agentName === '') {
            throw new RuntimeException('Selected paying agent was not found.');
        }

        $folioName = 'Other Payer - ' . $agentName;
        $stmt = $pdo->prepare("SELECT id FROM folios WHERE order_id = :order_id AND folio_type = 'other' AND folio_name = :folio_name ORDER BY id ASC LIMIT 1");
        $stmt->execute([':order_id' => $orderId, ':folio_name' => $folioName]);
        $folioId = (int)($stmt->fetchColumn() ?: 0);
        if ($folioId > 0) return $folioId;

        $stmt = $pdo->prepare("\n            INSERT INTO folios\n            (property_id, order_id, folio_no, folio_name, folio_type, status, total_charges, total_payments, balance)\n            VALUES\n            (:property_id, :order_id, :folio_no, :folio_name, 'other', 1, 0, 0, 0)\n        ");
        $stmt->execute([
            ':property_id' => $propertyId,
            ':order_id' => $orderId,
            ':folio_no' => 'FOL-O-' . $orderId . '-' . $agentId,
            ':folio_name' => $folioName,
        ]);
        return (int)$pdo->lastInsertId();
    }
}

try {
    ensure_booking_extra_agent_payers_table($pdo);
} catch (Throwable $e) {
    // Saving to Others will show the operational error if the table cannot be created.
}

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

    $stmt = $pdo->prepare("SELECT property_id, order_no FROM orders WHERE id=? LIMIT 1");
    $stmt->execute([$orderId]);
    $o = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$o) {
        throw new RuntimeException('Booking not found while creating master folio.');
    }

    $pdo->prepare("
        INSERT INTO folios (property_id, order_id, folio_no, folio_name, folio_type, status, total_charges, total_payments, balance)
        VALUES (?, ?, ?, ?, 'master', 1, 0, 0, 0)
    ")->execute([
        (int)($o['property_id'] ?? 0),
        $orderId,
        'FOL-M-' . $orderId,
        'Master Folio - ' . ($o['order_no'] ?? ('Booking #' . $orderId))
    ]);

    return (int)$pdo->lastInsertId();
}

/* ===========================
   BUILD ROOM FOLIOS SOURCE
=========================== */
function get_room_folios(PDO $pdo, int $orderId): array
{
    $rows = $pdo->prepare("
        SELECT 
            orm.id AS rooming_id,
            orm.room_id,
            orm.room_type_id,
            r.room_no,
            rt.title AS room_type,
            CONCAT(COALESCE(g.firstname,''),' ',COALESCE(g.lastname,'')) AS guest
        FROM order_rooming orm
        LEFT JOIN rooms r ON r.id = orm.room_id
        LEFT JOIN room_types rt ON rt.id = orm.room_type_id
        LEFT JOIN orders o ON o.id = orm.order_id
        LEFT JOIN guests g ON g.id = o.guest_id
        WHERE orm.order_id = ?
        ORDER BY COALESCE(r.room_no, ''), orm.id ASC
    ");
    $rows->execute([$orderId]);
    return $rows->fetchAll(PDO::FETCH_ASSOC);
}

if (!function_exists('get_guest_folios_for_order')) {
    function get_guest_folios_for_order(PDO $pdo, int $orderId): array
    {
        if ($orderId <= 0) {
            return [];
        }

        try {
            $stmt = $pdo->prepare("
                SELECT
                    f.id AS folio_id,
                    f.order_id,
                    f.rooming_id,
                    f.room_id,
                    f.occupant_id,
                    f.occupant_name,
                    f.folio_no,
                    f.folio_name,
                    f.balance,
                    r.room_no,
                    rt.title AS room_type
                FROM folios f
                LEFT JOIN rooms r ON r.id = f.room_id
                LEFT JOIN order_rooming orm ON orm.id = f.rooming_id
                LEFT JOIN room_types rt ON rt.id = orm.room_type_id
                WHERE f.order_id = :order_id
                  AND f.folio_type = 'guest'
                  AND COALESCE(f.status, 1) = 1
                ORDER BY COALESCE(r.room_no, ''), f.occupant_name ASC, f.id ASC
            ");
            $stmt->execute([':order_id' => $orderId]);
            return $stmt->fetchAll(PDO::FETCH_ASSOC);
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('get_guest_folio_id_from_post_value')) {
    function get_guest_folio_id_from_post_value(string $postTo): int
    {
        $postTo = trim($postTo);
        if (substr($postTo, 0, 6) !== 'guest:') {
            return 0;
        }
        return (int)substr($postTo, 6);
    }
}

if (!function_exists('validate_guest_folio_target')) {
    function validate_guest_folio_target(PDO $pdo, int $orderId, int $folioId): int
    {
        if ($folioId <= 0) {
            throw new RuntimeException('Invalid guest folio selection.');
        }

        $stmt = $pdo->prepare("
            SELECT id
            FROM folios
            WHERE id = :folio_id
              AND order_id = :order_id
              AND folio_type = 'guest'
              AND COALESCE(status, 1) = 1
            LIMIT 1
        ");
        $stmt->execute([
            ':folio_id' => $folioId,
            ':order_id' => $orderId,
        ]);

        $validId = (int)($stmt->fetchColumn() ?: 0);
        if ($validId <= 0) {
            throw new RuntimeException('Selected guest folio was not found or is no longer active.');
        }

        return $validId;
    }
}


if (!function_exists('next_folio_no_for_property')) {
    function next_folio_no_for_property(PDO $pdo, int $propertyId = 0): string
    {
        do {
            $prefix = 'FOL';
            if ($propertyId > 0) {
                $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);
                }
            }

            $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]);
            $exists = (int)$stmt->fetchColumn() > 0;
        } while ($exists);

        return $folioNo;
    }
}

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

        if ($folioId > 0) {
            return $folioId;
        }

        $stmt = $pdo->prepare("
            SELECT
                o.id,
                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 for folio creation.');
        }

        $guestName = trim(($order['firstname'] ?? '') . ' ' . ($order['lastname'] ?? ''));
        if ($guestName === '') {
            $guestName = 'Guest';
        }

        $propertyId = (int)($order['property_id'] ?? 0);
        $folioNo = next_folio_no_for_property($pdo, $propertyId);
        $folioName = ($order['order_no'] ?: ('Booking #' . $orderId)) . ' - ' . $guestName;

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

        return (int)$pdo->lastInsertId();
    }
}

if (!function_exists('recalculate_folio_totals')) {
    function recalculate_folio_totals(PDO $pdo, int $folioId): void
    {
        /*
           Keep this in line with payments.php accounting logic.
           Charges must exclude payment/reversal rows, otherwise voids/refunds can
           make booking totals wrong on the reservation list.
        */
        $stmt = $pdo->prepare("\n            SELECT\n                COALESCE(SUM(\n                    CASE\n                        WHEN COALESCE(is_voided, 0) = 0\n                             AND LOWER(COALESCE(tx_type, '')) NOT IN (\n                                'payment', 'receipt', 'deposit', 'payment_received',\n                                'folio_payment', 'guest_payment',\n                                'payment_void', 'void_payment', 'payment_reversal',\n                                'reversal', 'refund', 'payment_refund'\n                             )\n                             AND LOWER(COALESCE(source_table, '')) NOT IN (\n                                'payment', 'payments', 'receipt', 'receipts'\n                             )\n                        THEN COALESCE(debit, 0)\n                        ELSE 0\n                    END\n                ), 0) AS total_charges,\n\n                GREATEST(\n                    0,\n                    COALESCE(SUM(\n                        CASE\n                            WHEN COALESCE(is_voided, 0) = 0\n                                 AND (\n                                    LOWER(COALESCE(tx_type, '')) IN (\n                                        'payment', 'receipt', 'deposit', 'payment_received',\n                                        'folio_payment', 'guest_payment'\n                                    )\n                                    OR LOWER(COALESCE(source_table, '')) IN (\n                                        'payment', 'payments', 'receipt', 'receipts'\n                                    )\n                                 )\n                                 AND COALESCE(credit, 0) > 0\n                            THEN COALESCE(credit, 0)\n\n                            WHEN COALESCE(is_voided, 0) = 0\n                                 AND (\n                                    LOWER(COALESCE(tx_type, '')) IN (\n                                        'payment_void', 'void_payment', 'payment_reversal',\n                                        'reversal', 'refund', 'payment_refund'\n                                    )\n                                    OR LOWER(COALESCE(description, '')) LIKE '%void%'\n                                    OR LOWER(COALESCE(description, '')) LIKE '%reversal%'\n                                    OR LOWER(COALESCE(description, '')) LIKE '%refund%'\n                                 )\n                                 AND COALESCE(debit, 0) > 0\n                            THEN -1 * COALESCE(debit, 0)\n\n                            WHEN COALESCE(is_voided, 0) = 0\n                                 AND (\n                                    LOWER(COALESCE(tx_type, '')) IN (\n                                        'payment_void', 'void_payment', 'payment_reversal',\n                                        'reversal', 'refund', 'payment_refund'\n                                    )\n                                    OR LOWER(COALESCE(description, '')) LIKE '%void%'\n                                    OR LOWER(COALESCE(description, '')) LIKE '%reversal%'\n                                    OR LOWER(COALESCE(description, '')) LIKE '%refund%'\n                                 )\n                                 AND COALESCE(credit, 0) < 0\n                            THEN COALESCE(credit, 0)\n\n                            ELSE 0\n                        END\n                    ), 0)\n                ) AS total_payments\n            FROM folio_transactions\n            WHERE folio_id = :folio_id\n        ");
        $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;

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

if (!function_exists('recalculate_order_folio_totals')) {
    function recalculate_order_folio_totals(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) {
            recalculate_folio_totals($pdo, (int)$folioId);
        }
        sync_order_payment_status($pdo, $orderId);
    }
}

if (!function_exists('calculate_saved_room_charge_total')) {
    function calculate_saved_room_charge_total(
        int $adults,
        int $children,
        float $sharingRate,
        float $singleRate,
        float $singleSupplement,
        float $childRatePercent
    ): array {
        $adults = max(0, $adults);
        $children = max(0, $children);
        $childRatePercent = max(0, $childRatePercent);

        if ($adults <= 0) {
            return ['room_total' => 0.00, 'rate_type' => 'Empty'];
        }

        if ($adults === 1) {
            $adultTotal = $singleRate > 0 ? $singleRate : ($sharingRate + $singleSupplement);
            $rateType = $singleRate > 0 ? 'Single' : 'Sharing + Supplement';
        } else {
            $adultTotal = $adults * $sharingRate;
            $rateType = 'Sharing';
        }

        $childTotal = $children * ($sharingRate * ($childRatePercent / 100));
        return ['room_total' => round($adultTotal + $childTotal, 2), 'rate_type' => $rateType];
    }
}

if (!function_exists('ensure_room_charges_posted_to_master_folio')) {
    function ensure_room_charges_posted_to_master_folio(PDO $pdo, int $orderId): void
    {
        $folioId = get_or_create_master_folio($pdo, $orderId);

        $stmt = $pdo->prepare("\n            SELECT COUNT(*)\n            FROM folio_transactions\n            WHERE folio_id = :folio_id\n              AND order_id = :order_id\n              AND LOWER(COALESCE(tx_type, '')) = 'room_charge'\n        ");
        $stmt->execute([':folio_id' => $folioId, ':order_id' => $orderId]);
        if ((int)$stmt->fetchColumn() > 0) {
            recalculate_folio_totals($pdo, $folioId);
            return;
        }

        $stmt = $pdo->prepare("\n            SELECT\n                property_id, date, sharing_rate, single_rate, single_supplement,\n                child_rate_percent, rate_plan_id, rate_plan_title,\n                rate_plan_currency, rate_plan_season_label\n            FROM rel_orders_prices\n            WHERE order_id = :order_id\n            ORDER BY date ASC\n        ");
        $stmt->execute([':order_id' => $orderId]);
        $nightRows = $stmt->fetchAll(PDO::FETCH_ASSOC);

        $stmt = $pdo->prepare("\n            SELECT\n                rmg.property_id, rmg.room_no, rmg.room_type_id, rmg.adults, rmg.children,\n                rt.title AS room_type_title\n            FROM order_rooming rmg\n            LEFT JOIN room_types rt ON rt.id = rmg.room_type_id\n            WHERE rmg.order_id = :order_id\n            ORDER BY rmg.room_no ASC, rmg.id ASC\n        ");
        $stmt->execute([':order_id' => $orderId]);
        $roomingRows = $stmt->fetchAll(PDO::FETCH_ASSOC);

        if (!$nightRows || !$roomingRows) {
            recalculate_folio_totals($pdo, $folioId);
            return;
        }

        $folioTxHasPropertyId = extras_db_column_exists($pdo, 'folio_transactions', 'property_id');
        if ($folioTxHasPropertyId) {
            $insert = $pdo->prepare("\n                INSERT INTO folio_transactions\n                (property_id, folio_id, order_id, tx_date, tx_type, description, debit, credit, source_table, source_id)\n                VALUES\n                (:property_id, :folio_id, :order_id, :tx_date, 'room_charge', :description, :debit, 0, 'rel_orders_prices', :source_id)\n            ");
        } else {
            $insert = $pdo->prepare("\n                INSERT INTO folio_transactions\n                (folio_id, order_id, tx_date, tx_type, description, debit, credit, source_table, source_id)\n                VALUES\n                (:folio_id, :order_id, :tx_date, 'room_charge', :description, :debit, 0, 'rel_orders_prices', :source_id)\n            ");
        }

        $seq = 1;
        foreach ($nightRows as $night) {
            $stayDate = (string)$night['date'];
            foreach ($roomingRows as $slot) {
                $calc = calculate_saved_room_charge_total(
                    (int)$slot['adults'],
                    (int)$slot['children'],
                    (float)$night['sharing_rate'],
                    (float)$night['single_rate'],
                    (float)$night['single_supplement'],
                    (float)$night['child_rate_percent']
                );

                $roomCharge = (float)$calc['room_total'];
                if ($roomCharge <= 0) {
                    continue;
                }

                $roomTypeTitle = trim((string)($slot['room_type_title'] ?? ''));
                $desc = 'Room Slot ' . (int)$slot['room_no'];
                if ($roomTypeTitle !== '') {
                    $desc .= ' (' . $roomTypeTitle . ')';
                }
                $desc .= ' charge for ' . $stayDate;
                if (!empty($night['rate_plan_title'])) {
                    $desc .= ' | ' . (string)$night['rate_plan_title'];
                }
                if (!empty($night['rate_plan_currency'])) {
                    $desc .= ' (' . (string)$night['rate_plan_currency'] . ')';
                }
                if (!empty($night['rate_plan_season_label'])) {
                    $desc .= ' - ' . (string)$night['rate_plan_season_label'];
                }
                $desc .= ' | ' . $calc['rate_type'];
                $desc .= ' | ' . (int)$slot['adults'] . 'A' . ((int)$slot['children'] > 0 ? '/' . (int)$slot['children'] . 'C' : '');

                $insertParams = [
                    ':folio_id'    => $folioId,
                    ':order_id'    => $orderId,
                    ':tx_date'     => $stayDate . ' 00:00:00',
                    ':description' => $desc,
                    ':debit'       => round($roomCharge, 2),
                    ':source_id'   => $seq,
                ];
                if ($folioTxHasPropertyId) {
                    $insertParams[':property_id'] = (int)($night['property_id'] ?: $slot['property_id']);
                }
                $insert->execute($insertParams);
                $seq++;
            }
        }

        recalculate_folio_totals($pdo, $folioId);
    }
}

/* ===========================
   CREATE ROOM FOLIO IF NEEDED
=========================== */
function get_or_create_room_folio(PDO $pdo, int $orderId, array $rooming): int
{
    $roomingId = (int)($rooming['rooming_id'] ?? 0);
    if ($roomingId <= 0) {
        throw new RuntimeException('Invalid room folio target.');
    }

    $stmt = $pdo->prepare("
        SELECT id
        FROM folios
        WHERE order_id = ? AND rooming_id = ? AND folio_type = 'room'
        LIMIT 1
    ");
    $stmt->execute([$orderId, $roomingId]);
    $id = (int)($stmt->fetchColumn() ?: 0);
    if ($id > 0) {
        return $id;
    }

    $stmt = $pdo->prepare("
        SELECT property_id, order_no
        FROM orders
        WHERE id = ?
        LIMIT 1
    ");
    $stmt->execute([$orderId]);
    $order = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$order) {
        throw new RuntimeException('Booking not found while creating room folio.');
    }

    $roomNo = trim((string)($rooming['room_no'] ?? ''));
    $guest  = trim((string)($rooming['guest'] ?? ''));
    if ($guest === '') {
        $guest = 'Guest';
    }

    $roomType = trim((string)($rooming['room_type'] ?? ''));
    $roomNoLabel = ($roomNo !== '' ? $roomNo : 'N/A');
    $folioNo = 'FOL-R-' . $roomNoLabel . '-' . $orderId;

    $folioName = 'Room ' . $roomNoLabel;
    if ($roomType !== '') {
        $folioName .= ' • ' . $roomType;
    }
    $folioName .= ' - ' . $guest;

    $pdo->prepare("
        INSERT INTO folios
        (
            order_id,
            property_id,
            folio_no,
            folio_name,
            folio_type,
            room_id,
            rooming_id,
            status,
            total_charges,
            total_payments,
            balance
        )
        VALUES
        (
            ?, ?, ?, ?, 'room', ?, ?, 1, 0, 0, 0
        )
    ")->execute([
        $orderId,
        (int)($order['property_id'] ?? 0),
        $folioNo,
        $folioName,
        (int)($rooming['room_id'] ?? 0),
        $roomingId
    ]);

    return (int)$pdo->lastInsertId();
}

/* ===========================
   SYNC ORDER PAYMENT STATUS
   (SUM ALL FOLIOS FOR BOOKING)
=========================== */
if (!function_exists('sync_order_payment_status')) {
    function sync_order_payment_status(PDO $pdo, int $orderId): void
    {
        $stmt = $pdo->prepare("
            SELECT
                COALESCE(SUM(total_charges), 0) AS total_charges,
                COALESCE(SUM(total_payments), 0) AS total_payments,
                COALESCE(SUM(balance), 0) AS total_balance
            FROM folios
            WHERE order_id = :order_id
        ");
        $stmt->execute([':order_id' => $orderId]);
        $folio = $stmt->fetch(PDO::FETCH_ASSOC);

        $payments = (float)($folio['total_payments'] ?? 0);
        $balance  = (float)($folio['total_balance'] ?? 0);

        if ($payments <= 0.00001) {
            $status = 0;
        } elseif ($balance <= 0.00001) {
            $status = 2;
        } else {
            $status = 1;
        }

        $pdo->prepare("
            UPDATE orders
            SET payment_status = :payment_status
            WHERE id = :id
        ")->execute([
            ':payment_status' => $status,
            ':id'             => $orderId,
        ]);
    }
}

/* ===========================
   LOAD ALL BOOKING FOLIOS + TX
=========================== */
function get_booking_folios_with_transactions(PDO $pdo, int $orderId): array
{
    $stmt = $pdo->prepare("
        SELECT
            f.*,
            r.room_no,
            ort.title AS room_type,
            CASE
                WHEN f.folio_type = 'master' THEN 'Master Folio'
                WHEN f.folio_type = 'room' THEN CONCAT('Room ', COALESCE(r.room_no, 'N/A'), CASE WHEN ort.title IS NOT NULL AND ort.title <> '' THEN CONCAT(' • ', ort.title) ELSE '' END)
                WHEN f.folio_type = 'guest' THEN 'Guest Folio'
                WHEN f.folio_type = 'other' THEN COALESCE(NULLIF(f.folio_name,''), 'Other Payer Folio')
                ELSE CONCAT(UCASE(LEFT(f.folio_type,1)), SUBSTRING(f.folio_type,2), ' Folio')
            END AS folio_label
        FROM folios f
        LEFT JOIN rooms r ON r.id = f.room_id
        LEFT JOIN order_rooming orm ON orm.id = f.rooming_id
        LEFT JOIN room_types ort ON ort.id = orm.room_type_id
        WHERE f.order_id = :order_id
        ORDER BY
            CASE f.folio_type
                WHEN 'master' THEN 1
                WHEN 'room' THEN 2
                WHEN 'guest' THEN 3
                WHEN 'other' THEN 4
                ELSE 9
            END,
            f.id ASC
    ");
    $stmt->execute([':order_id' => $orderId]);
    $folios = $stmt->fetchAll(PDO::FETCH_ASSOC);

    if (!$folios) {
        return [];
    }

    $folioIds = [];
    foreach ($folios as $f) {
        $folioIds[] = (int)$f['id'];
    }

    $placeholders = implode(',', array_fill(0, count($folioIds), '?'));

    $txStmt = $pdo->prepare("
        SELECT *
        FROM folio_transactions
        WHERE folio_id IN ($placeholders)
        ORDER BY tx_date DESC, id DESC
    ");
    $txStmt->execute($folioIds);
    $txRows = $txStmt->fetchAll(PDO::FETCH_ASSOC);

    $txIds = [];
    foreach ($txRows as $txForItems) {
        $txId = (int)($txForItems['id'] ?? 0);
        if ($txId > 0) $txIds[] = $txId;
    }
    $itemsByTx = [];
    if ($txIds) {
        try {
            ensure_extra_charge_item_link_table($pdo);
            $itemPlaceholders = implode(',', array_fill(0, count($txIds), '?'));
            $itemStmt = $pdo->prepare("\n                SELECT *\n                FROM extra_charge_items\n                WHERE folio_transaction_id IN ($itemPlaceholders)\n                ORDER BY folio_transaction_id ASC, line_no ASC, id ASC\n            ");
            $itemStmt->execute($txIds);
            foreach ($itemStmt->fetchAll(PDO::FETCH_ASSOC) as $extraItemRow) {
                $itemsByTx[(int)$extraItemRow['folio_transaction_id']][] = $extraItemRow;
            }
        } catch (Throwable $e) {
            $itemsByTx = [];
        }
    }

    $groupedTx = [];
    foreach ($txRows as $tx) {
        $fid = (int)$tx['folio_id'];
        if (!isset($groupedTx[$fid])) {
            $groupedTx[$fid] = [];
        }
        $tx['extra_items'] = $itemsByTx[(int)($tx['id'] ?? 0)] ?? [];
        $groupedTx[$fid][] = $tx;
    }

    foreach ($folios as &$folio) {
        $fid = (int)$folio['id'];
        $folio['transactions'] = $groupedTx[$fid] ?? [];
    }
    unset($folio);

    return $folios;
}


if (!function_exists('extras_normalize_label')) {
    function extras_normalize_label(string $value): string
    {
        $value = strtolower(trim($value));
        $value = preg_replace('/[^a-z0-9]+/', ' ', $value);
        return trim((string)$value);
    }
}

if (!function_exists('extras_category_source_key')) {
    function extras_category_source_key(string $categoryLabel): string
    {
        $label = extras_normalize_label($categoryLabel);
        if ($label === 'restaurant' || strpos($label, 'restaurant') !== false || $label === 'food') {
            return 'food';
        }
        if ($label === 'bar' || strpos($label, 'bar') !== false || $label === 'drinks' || $label === 'drink') {
            return 'drinks';
        }
        if ($label === 'spar' || $label === 'spa' || strpos($label, 'spar') !== false || strpos($label, 'spa') !== false) {
            return 'spar';
        }
        if ($label === 'curio' || strpos($label, 'curio') !== false) {
            return 'curio';
        }
        return 'services';
    }
}

if (!function_exists('extras_build_menu_descendant_ids')) {
    function extras_build_menu_descendant_ids(array $categories, string $rootName): array
    {
        $rootName = extras_normalize_label($rootName);
        $byParent = [];
        $roots = [];
        foreach ($categories as $cat) {
            $id = (int)($cat['id'] ?? 0);
            if ($id <= 0) continue;
            $parentId = (int)($cat['parent_id'] ?? 0);
            $byParent[$parentId][] = $id;
            if (extras_normalize_label((string)($cat['category_name'] ?? '')) === $rootName) {
                $roots[] = $id;
            }
        }
        $out = [];
        $walk = function(int $id) use (&$walk, &$out, &$byParent): void {
            $out[$id] = $id;
            foreach (($byParent[$id] ?? []) as $childId) {
                $walk((int)$childId);
            }
        };
        foreach ($roots as $rootId) $walk((int)$rootId);
        return array_values($out);
    }
}

if (!function_exists('extras_table_exists')) {
    function extras_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 = :table_name");
            $stmt->execute([':table_name' => $tableName]);
            return $cache[$tableName] = ((int)$stmt->fetchColumn() > 0);
        } catch (Throwable $e) {
            return $cache[$tableName] = false;
        }
    }
}

if (!function_exists('extras_column_exists')) {
    function extras_column_exists(PDO $pdo, string $tableName, string $columnName): bool
    {
        static $cache = [];
        $key = $tableName . '.' . $columnName;
        if (array_key_exists($key, $cache)) return $cache[$key];
        try {
            $stmt = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = :table_name AND column_name = :column_name");
            $stmt->execute([':table_name' => $tableName, ':column_name' => $columnName]);
            return $cache[$key] = ((int)$stmt->fetchColumn() > 0);
        } catch (Throwable $e) {
            return $cache[$key] = false;
        }
    }
}


if (!function_exists('extras_should_sync_to_restaurant_calendar')) {
    function extras_should_sync_to_restaurant_calendar(string $categoryLabel, string $sourceTable = ''): bool
    {
        $label = extras_normalize_label($categoryLabel);
        $source = strtolower(trim($sourceTable));

        if ($source === 'restaurant_menu_items') {
            return true;
        }

        foreach (['restaurant', 'food', 'bar', 'drink', 'drinks', 'spa', 'spar'] as $word) {
            if ($label === $word || strpos($label, $word) !== false) {
                return true;
            }
        }

        return false;
    }
}

if (!function_exists('extras_meal_period_from_category')) {
    function extras_meal_period_from_category(string $categoryLabel, string $txDate): string
    {
        $label = extras_normalize_label($categoryLabel);
        if (strpos($label, 'bar') !== false || strpos($label, 'drink') !== false) return 'dinner';
        if (strpos($label, 'spa') !== false || strpos($label, 'spar') !== false) return 'other';

        $ts = strtotime($txDate);
        $hour = $ts ? (int)date('H', $ts) : (int)date('H');
        if ($hour >= 5 && $hour < 11) return 'breakfast';
        if ($hour >= 11 && $hour < 16) return 'lunch';
        if ($hour >= 16 && $hour < 23) return 'dinner';
        return 'other';
    }
}

if (!function_exists('extras_restaurant_order_no')) {
    function extras_restaurant_order_no(PDO $pdo): string
    {
        $prefix = 'RST-EX-' . date('ymd') . '-';
        try {
            $stmt = $pdo->prepare("SELECT COUNT(*) FROM restaurant_orders WHERE order_no LIKE :prefix");
            $stmt->execute([':prefix' => $prefix . '%']);
            return $prefix . str_pad((string)((int)$stmt->fetchColumn() + 1), 4, '0', STR_PAD_LEFT);
        } catch (Throwable $e) {
            return $prefix . random_int(1000, 9999);
        }
    }
}

if (!function_exists('extras_prepare_restaurant_sync_tables')) {
    function extras_prepare_restaurant_sync_tables(PDO $pdo): void
    {
        $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_orders (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
            property_id INT UNSIGNED NULL,
            area_id INT UNSIGNED NULL,
            table_id INT UNSIGNED NULL,
            order_no VARCHAR(60) NOT NULL,
            order_name VARCHAR(180) NULL,
            meal_period VARCHAR(30) NULL,
            close_method VARCHAR(80) NULL,
            order_type ENUM('table','room','walkin','takeaway') NOT NULL DEFAULT 'room',
            linked_order_id INT UNSIGNED NULL,
            linked_folio_id INT UNSIGNED NULL,
            guest_name VARCHAR(180) NULL,
            room_no VARCHAR(80) NULL,
            covers INT UNSIGNED NOT NULL DEFAULT 1,
            status ENUM('open','sent','preparing','served','bill_requested','closed','void') NOT NULL DEFAULT 'closed',
            subtotal DECIMAL(14,2) NOT NULL DEFAULT 0.00,
            tax_total DECIMAL(14,2) NOT NULL DEFAULT 0.00,
            total_amount DECIMAL(14,2) NOT NULL DEFAULT 0.00,
            payment_status ENUM('unpaid','part_paid','paid','room_charged','void') NOT NULL DEFAULT 'room_charged',
            opened_by INT UNSIGNED NULL,
            closed_by INT UNSIGNED NULL,
            opened_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            closed_at DATETIME NULL,
            notes TEXT NULL,
            created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uq_rest_order_no(order_no),
            INDEX idx_rest_order_table_status(table_id,status),
            INDEX idx_rest_order_linked_order(linked_order_id),
            INDEX idx_rest_order_linked_folio(linked_folio_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        $restaurantOrderCols = [
            'order_name' => "`order_name` VARCHAR(180) NULL AFTER `order_no`",
            'meal_period' => "`meal_period` VARCHAR(30) NULL AFTER `order_name`",
            'close_method' => "`close_method` VARCHAR(80) NULL AFTER `meal_period`",
            'linked_order_id' => "`linked_order_id` INT UNSIGNED NULL AFTER `order_type`",
            'linked_folio_id' => "`linked_folio_id` INT UNSIGNED NULL AFTER `linked_order_id`",
            'guest_name' => "`guest_name` VARCHAR(180) NULL AFTER `linked_folio_id`",
            'room_no' => "`room_no` VARCHAR(80) NULL AFTER `guest_name`",
            'payment_status' => "`payment_status` ENUM('unpaid','part_paid','paid','room_charged','void') NOT NULL DEFAULT 'unpaid' AFTER `total_amount`",
            'closed_at' => "`closed_at` DATETIME NULL AFTER `opened_at`",
            'notes' => "`notes` TEXT NULL AFTER `closed_at`",
        ];
        foreach ($restaurantOrderCols as $col => $ddl) {
            extras_try_add_column($pdo, 'restaurant_orders', $col, $ddl);
        }

        $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_order_items (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
            restaurant_order_id BIGINT UNSIGNED NOT NULL,
            menu_item_id INT UNSIGNED NULL,
            item_name VARCHAR(255) NOT NULL,
            quantity DECIMAL(14,4) NOT NULL DEFAULT 1.0000,
            unit_price DECIMAL(14,2) NOT NULL DEFAULT 0.00,
            line_total DECIMAL(14,2) NOT NULL DEFAULT 0.00,
            notes TEXT NULL,
            created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
            INDEX idx_rest_item_order(restaurant_order_id),
            INDEX idx_rest_item_menu(menu_item_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        $restaurantItemCols = [
            'restaurant_order_id' => "`restaurant_order_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER `id`",
            'menu_item_id' => "`menu_item_id` INT UNSIGNED NULL AFTER `restaurant_order_id`",
            'item_name' => "`item_name` VARCHAR(255) NOT NULL DEFAULT 'Item' AFTER `menu_item_id`",
            'quantity' => "`quantity` DECIMAL(14,4) NOT NULL DEFAULT 1.0000 AFTER `item_name`",
            'unit_price' => "`unit_price` DECIMAL(14,2) NOT NULL DEFAULT 0.00 AFTER `quantity`",
            'line_total' => "`line_total` DECIMAL(14,2) NOT NULL DEFAULT 0.00 AFTER `unit_price`",
            'notes' => "`notes` TEXT NULL AFTER `line_total`",
            'created_at' => "`created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP AFTER `notes`",
        ];
        foreach ($restaurantItemCols as $col => $ddl) {
            extras_try_add_column($pdo, 'restaurant_order_items', $col, $ddl);
        }

        $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_reservations (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
            property_id INT UNSIGNED NULL,
            restaurant_area_id INT UNSIGNED NULL,
            table_id INT UNSIGNED NULL,
            order_id INT UNSIGNED NULL,
            guest_id INT UNSIGNED NULL,
            room_no VARCHAR(80) NULL,
            guest_name VARCHAR(180) NOT NULL,
            reservation_date DATE NOT NULL,
            reservation_time TIME NOT NULL,
            duration_minutes INT UNSIGNED NOT NULL DEFAULT 90,
            covers INT UNSIGNED NOT NULL DEFAULT 1,
            meal_period ENUM('breakfast','lunch','dinner','other') NOT NULL DEFAULT 'other',
            source ENUM('room_guest','walkin','external') NOT NULL DEFAULT 'room_guest',
            status ENUM('booked','seated','no_show','cancelled','completed') NOT NULL DEFAULT 'completed',
            notes TEXT NULL,
            restaurant_order_id BIGINT UNSIGNED NULL,
            created_by INT UNSIGNED NULL,
            created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            INDEX idx_rest_res_date_time (reservation_date, reservation_time),
            INDEX idx_rest_res_order (order_id),
            INDEX idx_rest_res_rest_order (restaurant_order_id),
            INDEX idx_rest_res_status (status)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        $reservationCols = [
            'property_id' => "`property_id` INT UNSIGNED NULL AFTER `id`",
            'restaurant_area_id' => "`restaurant_area_id` INT UNSIGNED NULL AFTER `property_id`",
            'table_id' => "`table_id` INT UNSIGNED NULL AFTER `restaurant_area_id`",
            'order_id' => "`order_id` INT UNSIGNED NULL AFTER `table_id`",
            'guest_id' => "`guest_id` INT UNSIGNED NULL AFTER `order_id`",
            'room_no' => "`room_no` VARCHAR(80) NULL AFTER `guest_id`",
            'guest_name' => "`guest_name` VARCHAR(180) NOT NULL DEFAULT 'Guest' AFTER `room_no`",
            'reservation_date' => "`reservation_date` DATE NOT NULL DEFAULT (CURRENT_DATE) AFTER `guest_name`",
            'reservation_time' => "`reservation_time` TIME NOT NULL DEFAULT '19:00:00' AFTER `reservation_date`",
            'duration_minutes' => "`duration_minutes` INT UNSIGNED NOT NULL DEFAULT 90 AFTER `reservation_time`",
            'covers' => "`covers` INT UNSIGNED NOT NULL DEFAULT 1 AFTER `duration_minutes`",
            'meal_period' => "`meal_period` ENUM('breakfast','lunch','dinner','other') NOT NULL DEFAULT 'other' AFTER `covers`",
            'source' => "`source` ENUM('room_guest','walkin','external') NOT NULL DEFAULT 'room_guest' AFTER `meal_period`",
            'status' => "`status` ENUM('booked','seated','no_show','cancelled','completed') NOT NULL DEFAULT 'completed' AFTER `source`",
            'notes' => "`notes` TEXT NULL AFTER `status`",
            'restaurant_order_id' => "`restaurant_order_id` BIGINT UNSIGNED NULL AFTER `notes`",
            'created_by' => "`created_by` INT UNSIGNED NULL AFTER `restaurant_order_id`",
        ];
        foreach ($reservationCols as $col => $ddl) {
            extras_try_add_column($pdo, 'restaurant_reservations', $col, $ddl);
        }
    }
}

if (!function_exists('extras_get_booking_context')) {
    function extras_get_booking_context(PDO $pdo, int $orderId): array
    {
        $stmt = $pdo->prepare("\n            SELECT\n                o.id,\n                o.property_id,\n                o.order_no,\n                o.guest_id,\n                COALESCE(g.firstname, '') AS firstname,\n                COALESCE(g.lastname, '') AS lastname\n            FROM orders o\n            LEFT JOIN guests g ON g.id = o.guest_id\n            WHERE o.id = :id\n            LIMIT 1\n        ");
        $stmt->execute([':id' => $orderId]);
        $order = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$order) {
            throw new RuntimeException('Booking not found while creating restaurant calendar link.');
        }

        $roomNo = '';
        try {
            $stmtRoom = $pdo->prepare("\n                SELECT COALESCE(r.room_no, orm.room_no, '') AS room_no\n                FROM order_rooming orm\n                LEFT JOIN rooms r ON r.id = orm.room_id\n                WHERE orm.order_id = :order_id\n                ORDER BY orm.id ASC\n                LIMIT 1\n            ");
            $stmtRoom->execute([':order_id' => $orderId]);
            $roomNo = trim((string)($stmtRoom->fetchColumn() ?: ''));
        } catch (Throwable $e) {
            $roomNo = '';
        }

        $guestName = trim((string)($order['firstname'] ?? '') . ' ' . (string)($order['lastname'] ?? ''));
        if ($guestName === '') {
            $guestName = 'Guest';
        }

        return [
            'property_id' => (int)($order['property_id'] ?? 0),
            'order_no' => (string)($order['order_no'] ?? ''),
            'guest_id' => (int)($order['guest_id'] ?? 0),
            'guest_name' => $guestName,
            'room_no' => $roomNo,
        ];
    }
}

if (!function_exists('extras_sync_extra_to_restaurant_calendar')) {
    function extras_sync_extra_to_restaurant_calendar(
        PDO $pdo,
        int $orderId,
        int $propertyId,
        int $folioId,
        int $folioTransactionId,
        string $txDate,
        array $line,
        string $fullDescription
    ): void {
        if (!extras_should_sync_to_restaurant_calendar((string)($line['category_label'] ?? ''), (string)($line['source_table'] ?? ''))) {
            return;
        }

        try {
            extras_prepare_restaurant_sync_tables($pdo);

            $ctx = extras_get_booking_context($pdo, $orderId);
            if ($propertyId <= 0) $propertyId = (int)($ctx['property_id'] ?? 0);

            $mealPeriod = extras_meal_period_from_category((string)($line['category_label'] ?? ''), $txDate);
            $dateOnly = date('Y-m-d', strtotime($txDate) ?: time());
            $timeOnly = date('H:i:s', strtotime($txDate . ' 00:00:00') ?: time());
            $category = trim((string)($line['category_label'] ?? 'Extra'));
            $itemName = trim((string)($line['item_name'] ?? $line['description'] ?? 'Extra Item'));
            if ($itemName === '') $itemName = 'Extra Item';

            $orderName = trim($category . ' Extra - ' . $ctx['guest_name']);
            $notes = 'Auto-created from Extras posting. Folio transaction #' . $folioTransactionId . '. ' . $fullDescription;

            $stmtOrder = $pdo->prepare("\n                INSERT INTO restaurant_orders\n                (property_id, order_no, order_name, meal_period, close_method, order_type, linked_order_id, linked_folio_id,\n                 guest_name, room_no, covers, status, subtotal, tax_total, total_amount, payment_status, opened_by, closed_by, opened_at, closed_at, notes)\n                VALUES\n                (:property_id, :order_no, :order_name, :meal_period, :close_method, 'room', :linked_order_id, :linked_folio_id,\n                 :guest_name, :room_no, 1, 'closed', :subtotal, 0, :total_amount, 'room_charged', :opened_by, :closed_by, :opened_at, NOW(), :notes)\n            ");
            $stmtOrder->execute([
                ':property_id' => $propertyId > 0 ? $propertyId : null,
                ':order_no' => extras_restaurant_order_no($pdo),
                ':order_name' => $orderName,
                ':meal_period' => $mealPeriod,
                ':close_method' => 'room',
                ':linked_order_id' => $orderId,
                ':linked_folio_id' => $folioId,
                ':guest_name' => $ctx['guest_name'],
                ':room_no' => $ctx['room_no'] !== '' ? $ctx['room_no'] : null,
                ':subtotal' => round((float)($line['amount'] ?? 0), 2),
                ':total_amount' => round((float)($line['amount'] ?? 0), 2),
                ':opened_by' => extras_current_user_id(),
                ':closed_by' => extras_current_user_id(),
                ':opened_at' => $dateOnly . ' ' . $timeOnly,
                ':notes' => $notes,
            ]);
            $restaurantOrderId = (int)$pdo->lastInsertId();

            $menuItemId = (strtolower((string)($line['source_table'] ?? '')) === 'restaurant_menu_items') ? (int)($line['source_id'] ?? 0) : null;
            $stmtItem = $pdo->prepare("\n                INSERT INTO restaurant_order_items\n                (restaurant_order_id, menu_item_id, item_name, quantity, unit_price, line_total, notes)\n                VALUES\n                (:restaurant_order_id, :menu_item_id, :item_name, :quantity, :unit_price, :line_total, :notes)\n            ");
            $stmtItem->execute([
                ':restaurant_order_id' => $restaurantOrderId,
                ':menu_item_id' => $menuItemId,
                ':item_name' => $itemName,
                ':quantity' => (float)($line['quantity'] ?? 1),
                ':unit_price' => round((float)($line['unit_price'] ?? 0), 2),
                ':line_total' => round((float)($line['amount'] ?? 0), 2),
                ':notes' => 'Linked to folio transaction #' . $folioTransactionId,
            ]);

            $stmtRes = $pdo->prepare("\n                INSERT INTO restaurant_reservations\n                (property_id, order_id, guest_id, room_no, guest_name, reservation_date, reservation_time, duration_minutes, covers, meal_period, source, status, notes, restaurant_order_id, created_by)\n                VALUES\n                (:property_id, :order_id, :guest_id, :room_no, :guest_name, :reservation_date, :reservation_time, 60, 1, :meal_period, 'room_guest', 'completed', :notes, :restaurant_order_id, :created_by)\n            ");
            $stmtRes->execute([
                ':property_id' => $propertyId > 0 ? $propertyId : null,
                ':order_id' => $orderId,
                ':guest_id' => (int)($ctx['guest_id'] ?? 0) > 0 ? (int)$ctx['guest_id'] : null,
                ':room_no' => $ctx['room_no'] !== '' ? $ctx['room_no'] : null,
                ':guest_name' => $ctx['guest_name'],
                ':reservation_date' => $dateOnly,
                ':reservation_time' => $timeOnly,
                ':meal_period' => $mealPeriod,
                ':notes' => $notes,
                ':restaurant_order_id' => $restaurantOrderId,
                ':created_by' => extras_current_user_id(),
            ]);

            try {
                $payload = json_encode([
                    'restaurant_order_id' => $restaurantOrderId,
                    'folio_transaction_id' => $folioTransactionId,
                    'reservation_date' => $dateOnly,
                    'meal_period' => $mealPeriod,
                ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
                ensure_extra_charge_audit_table($pdo);
                $stmtAudit = $pdo->prepare("\n                    INSERT INTO extra_charge_audit_log\n                    (order_id, folio_id, folio_transaction_id, service_id, action_type, standard_unit_price, override_unit_price,\n                     quantity, standard_amount, override_amount, reason, payload_json, action_by)\n                    VALUES\n                    (:order_id, :folio_id, :folio_transaction_id, :service_id, 'restaurant_calendar_sync', :standard_unit_price, :override_unit_price,\n                     :quantity, :standard_amount, :override_amount, :reason, :payload_json, :action_by)\n                ");
                $stmtAudit->execute([
                    ':order_id' => $orderId,
                    ':folio_id' => $folioId,
                    ':folio_transaction_id' => $folioTransactionId,
                    ':service_id' => (int)($line['service_id'] ?? 0),
                    ':standard_unit_price' => round((float)($line['standard_unit_price'] ?? 0), 2),
                    ':override_unit_price' => round((float)($line['unit_price'] ?? 0), 2),
                    ':quantity' => (float)($line['quantity'] ?? 1),
                    ':standard_amount' => round((float)($line['standard_amount'] ?? 0), 2),
                    ':override_amount' => round((float)($line['amount'] ?? 0), 2),
                    ':reason' => 'Synced extras posting to Restaurant Reservations / Calendar.',
                    ':payload_json' => $payload,
                    ':action_by' => extras_current_user_id(),
                ]);
            } catch (Throwable $auditError) {
                // Do not block the operational posting because of audit logging.
            }
        } catch (Throwable $syncError) {
            throw new RuntimeException('Extra was not fully linked to Restaurant Reservations / Calendar: ' . $syncError->getMessage());
        }
    }
}


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

$payingAgents = [];
try {
    $agentDisplayExpr = extras_agent_display_expression($pdo, 'a');
    $payingAgents = $pdo->query("
        SELECT a.id, {$agentDisplayExpr} AS agent_name
        FROM agents a
        ORDER BY agent_name ASC, a.id ASC
    ")->fetchAll(PDO::FETCH_ASSOC) ?: [];
} catch (Throwable $e) {
    $payingAgents = [];
}

$payingAgentsJson = json_encode(array_map(function (array $agent): array {
    return [
        'id' => (int)($agent['id'] ?? 0),
        'name' => trim((string)($agent['agent_name'] ?? '')),
    ];
}, $payingAgents), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (!is_string($payingAgentsJson)) {
    $payingAgentsJson = '[]';
}


$extraCategories = [];
$extraServices = [];
$extraItemOptions = [];
$extraItemOptionsJson = '{}';

try {
    $extraCategories = $pdo->query("SELECT id, category FROM tblservicecategory ORDER BY category ASC")->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
    $extraCategories = [];
    $errors[] = 'Could not load extra categories: ' . $e->getMessage();
}

try {
    extras_ensure_service_related_schema($pdo);

    $serviceSelectColumns = "s.id, s.service, s.price_per_person, s.category, c.category AS category_name";
    $serviceSelectColumns .= extras_column_exists($pdo, 'tblservices', 'requires_extra') ? ", COALESCE(s.requires_extra,0) AS requires_extra" : ", 0 AS requires_extra";
    $serviceSelectColumns .= extras_column_exists($pdo, 'tblservices', 'related_service_id_1') ? ", s.related_service_id_1" : ", NULL AS related_service_id_1";
    $serviceSelectColumns .= extras_column_exists($pdo, 'tblservices', 'related_service_id_2') ? ", s.related_service_id_2" : ", NULL AS related_service_id_2";

    $extraServices = $pdo->query("
        SELECT $serviceSelectColumns
        FROM tblservices s
        LEFT JOIN tblservicecategory c ON c.id = s.category
        WHERE COALESCE(s.is_active, 1) = 1
        ORDER BY c.category ASC, s.service ASC
    ")->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
    $extraServices = [];
    $errors[] = 'Could not load extra services: ' . $e->getMessage();
}
try {
    $serviceOptionById = [];
    foreach ($extraServices as $svc) {
        $serviceId = (int)($svc['id'] ?? 0);
        $categoryId = (int)($svc['category'] ?? 0);
        if ($serviceId <= 0 || $categoryId <= 0) continue;

        $serviceOptionById[$serviceId] = [
            'value' => 'service:' . $serviceId,
            'label' => (string)$svc['service'],
            'price' => (float)($svc['price_per_person'] ?? 0),
            'source' => 'tblservices',
            'source_id' => $serviceId,
            'item_type' => 'Service',
            'item_name' => (string)$svc['service'],
            'item_code' => '',
            'category_id' => $categoryId,
            'category_name' => (string)($svc['category_name'] ?? ''),
            'requires_extra' => (int)($svc['requires_extra'] ?? 0),
            'related_service_id_1' => (int)($svc['related_service_id_1'] ?? 0),
            'related_service_id_2' => (int)($svc['related_service_id_2'] ?? 0),
            'related_items' => [],
        ];
    }

    foreach ($serviceOptionById as $serviceId => $opt) {
        $relatedItems = [];
        foreach ([(int)($opt['related_service_id_1'] ?? 0), (int)($opt['related_service_id_2'] ?? 0)] as $relatedId) {
            if ($relatedId > 0 && $relatedId !== (int)$serviceId && isset($serviceOptionById[$relatedId])) {
                $related = $serviceOptionById[$relatedId];
                $related['related_items'] = [];
                $related['auto_related'] = 1;
                $related['parent_service_id'] = (int)$serviceId;
                $relatedItems[] = $related;
            }
        }
        if ($relatedItems) {
            $opt['requires_extra'] = 1;
            $opt['related_items'] = $relatedItems;
        }
        $categoryId = (int)($opt['category_id'] ?? 0);
        if ($categoryId > 0) {
            $extraItemOptions[$categoryId][] = $opt;
        }
    }

    $menuCategories = [];
    if (extras_table_exists($pdo, 'restaurant_menu_categories')) {
        $menuCategories = $pdo->query("
            SELECT id, parent_id, category_name
            FROM restaurant_menu_categories
            WHERE COALESCE(is_active, 1) = 1
            ORDER BY COALESCE(parent_id,0), sort_order, category_name
        ")->fetchAll(PDO::FETCH_ASSOC);
    }

    $menuItemsByRoot = ['food' => [], 'drinks' => [], 'spar' => []];
    if ($menuCategories && extras_table_exists($pdo, 'restaurant_menu_items')) {
        $sparRootIds = array_values(array_unique(array_merge(
            extras_build_menu_descendant_ids($menuCategories, 'Spar'),
            extras_build_menu_descendant_ids($menuCategories, 'Spa')
        )));

        $rootMap = [
            'food' => extras_build_menu_descendant_ids($menuCategories, 'Food'),
            'drinks' => extras_build_menu_descendant_ids($menuCategories, 'Drinks'),
            // Your Spar root category is saved as Spa / spa in restaurant_menu_categories.
            // Accept both names so all items under that root branch are available for Spar extras.
            'spar' => $sparRootIds,
        ];
        foreach ($rootMap as $rootKey => $catIds) {
            if (!$catIds) continue;
            $in = implode(',', array_fill(0, count($catIds), '?'));
            $stmt = $pdo->prepare("
                SELECT i.id, i.item_name, i.item_code, i.price, i.category_id, c.category_name
                FROM restaurant_menu_items i
                LEFT JOIN restaurant_menu_categories c ON c.id = i.category_id
                WHERE COALESCE(i.is_active,1) = 1 AND i.category_id IN ($in)
                ORDER BY COALESCE(c.category_name,''), i.sort_order, i.item_name
            ");
            $stmt->execute($catIds);
            foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $mi) {
                $code = trim((string)($mi['item_code'] ?? ''));
                $catName = trim((string)($mi['category_name'] ?? ''));
                $label = trim((string)$mi['item_name']);
                if ($code !== '') $label .= ' [' . $code . ']';
                if ($catName !== '') $label .= ' • ' . $catName;
                $menuItemsByRoot[$rootKey][] = [
                    'value' => 'menu:' . (int)$mi['id'],
                    'label' => $label,
                    'price' => (float)($mi['price'] ?? 0),
                    'source' => 'restaurant_menu_items',
                    'source_id' => (int)$mi['id'],
                    'item_type' => 'Menu Item',
                    'item_name' => trim((string)$mi['item_name']),
                    'item_code' => $code,
                    'category_name' => $catName,
                ];
            }
        }
    }

    $inventoryItemsAll = [];
    $curioItems = [];
    if (extras_table_exists($pdo, 'inventory_items')) {
        $categoryColumn = extras_column_exists($pdo, 'inventory_items', 'category') ? 'category' : (extras_column_exists($pdo, 'inventory_items', 'category_id') ? 'category_id' : '');
        $priceColumn = extras_column_exists($pdo, 'inventory_items', 'selling_price') ? 'selling_price' : (extras_column_exists($pdo, 'inventory_items', 'price') ? 'price' : (extras_column_exists($pdo, 'inventory_items', 'cost_price') ? 'cost_price' : '0'));
        $codeColumn = extras_column_exists($pdo, 'inventory_items', 'item_code') ? 'item_code' : (extras_column_exists($pdo, 'inventory_items', 'barcode') ? 'barcode' : "''");

        /*
           Bar extras now pick from ALL inventory_items, not restaurant_menu_items.
           This makes Bar behave like a stock/inventory charge source and avoids having
           to maintain bar products twice in the restaurant menu and in inventory.
        */
        $stmtAllInventory = $pdo->query("
            SELECT id, item_name, {$codeColumn} AS item_code, {$priceColumn} AS selling_price" . ($categoryColumn !== '' ? ", {$categoryColumn} AS inventory_category" : ", NULL AS inventory_category") . "
            FROM inventory_items
            WHERE COALESCE(is_active,1) = 1
            ORDER BY item_name ASC
        ");
        foreach ($stmtAllInventory->fetchAll(PDO::FETCH_ASSOC) as $ii) {
            $code = trim((string)($ii['item_code'] ?? ''));
            $label = trim((string)($ii['item_name'] ?? 'Inventory Item'));
            if ($code !== '') $label .= ' [' . $code . ']';

            $inventoryOption = [
                'value' => 'inventory:' . (int)$ii['id'],
                'label' => $label,
                'price' => (float)($ii['selling_price'] ?? 0),
                'source' => 'inventory_items',
                'source_id' => (int)$ii['id'],
                'item_type' => 'Inventory Item',
                'item_name' => trim((string)($ii['item_name'] ?? 'Inventory Item')),
                'item_code' => $code,
                'category_name' => 'Inventory',
            ];

            $inventoryItemsAll[] = $inventoryOption;

            // Keep the existing Curio behavior: Curio is inventory category 3.
            if ($categoryColumn !== '' && (int)($ii['inventory_category'] ?? 0) === 3) {
                $curioOption = $inventoryOption;
                $curioOption['category_name'] = 'Curio';
                $curioItems[] = $curioOption;
            }
        }
    }

    foreach ($extraCategories as $cat) {
        $categoryId = (int)($cat['id'] ?? 0);
        if ($categoryId <= 0) continue;
        $sourceKey = extras_category_source_key((string)($cat['category'] ?? ''));
        if ($sourceKey === 'food') $extraItemOptions[$categoryId] = $menuItemsByRoot['food'];
        elseif ($sourceKey === 'drinks') $extraItemOptions[$categoryId] = $inventoryItemsAll;
        elseif ($sourceKey === 'spar') $extraItemOptions[$categoryId] = $menuItemsByRoot['spar'];
        elseif ($sourceKey === 'curio') $extraItemOptions[$categoryId] = $curioItems;
    }

    $extraItemOptionsJson = json_encode($extraItemOptions, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
} catch (Throwable $e) {
    $extraItemOptionsJson = '{}';
    $errors[] = 'Could not prepare searchable extra item lists: ' . $e->getMessage();
}

$selectedPropertyId = (int)($_GET['property_id'] ?? $_POST['property_id'] ?? 0);
$orderId = (int)($_GET['order_id'] ?? $_POST['order_id'] ?? 0);
$errors = [];
$success = '';

$order = null;
$folio = null; // master folio summary
$transactions = [];
$bookingFolios = [];
$roomMixStr = '';


if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['move_folio_transaction'])) {
    $orderId = (int)($_POST['order_id'] ?? 0);
    $transactionId = (int)($_POST['folio_transaction_id'] ?? 0);
    $targetFolioId = (int)($_POST['target_folio_id'] ?? 0);
    $moveReason = trim((string)($_POST['move_reason'] ?? ''));

    if ($orderId <= 0) {
        $errors[] = 'Invalid booking selected.';
    }
    if ($transactionId <= 0) {
        $errors[] = 'Invalid transaction selected.';
    }
    if ($targetFolioId <= 0) {
        $errors[] = 'Please select the destination folio.';
    }

    if (!$errors) {
        try {
            $pdo->beginTransaction();
            move_folio_transaction_to_folio($pdo, $orderId, $transactionId, $targetFolioId, $moveReason);
            $pdo->commit();

            header('Location: extras.php?order_id=' . $orderId . '&moved=1');
            exit;
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            $errors[] = $e->getMessage();
        }
    }
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_extra_transaction'])) {
    $orderId = (int)($_POST['order_id'] ?? 0);
    $transactionId = (int)($_POST['folio_transaction_id'] ?? 0);

    if ($orderId <= 0) {
        $errors[] = 'Invalid booking selected.';
    }
    if ($transactionId <= 0) {
        $errors[] = 'Invalid extra selected.';
    }

    if (!$errors) {
        try {
            // Fetch the transaction and confirm it belongs to this booking.
            $stmt = $pdo->prepare("
                SELECT ft.id, ft.folio_id, ft.tx_type, ft.source_table, ft.debit, ft.credit
                FROM folio_transactions ft
                WHERE ft.id = :tx AND ft.order_id = :order_id
                LIMIT 1
            ");
            $stmt->execute([':tx' => $transactionId, ':order_id' => $orderId]);
            $tx = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$tx) {
                throw new RuntimeException('That extra could not be found for this booking.');
            }

            // Only extras may be deleted here - never room charges or payments.
            // This protects accommodation and money lines from accidental removal.
            $type = strtolower((string)($tx['tx_type'] ?? ''));
            $src  = strtolower((string)($tx['source_table'] ?? ''));
            $protectedTypes = [
                'room_charge', 'accommodation',
                'payment', 'receipt', 'deposit', 'payment_received', 'folio_payment',
                'guest_payment', 'payment_void', 'void_payment', 'payment_reversal',
                'reversal', 'refund', 'payment_refund',
            ];
            $protectedSources = ['rel_orders_prices', 'payment', 'payments', 'receipt', 'receipts'];
            if (in_array($type, $protectedTypes, true) || in_array($src, $protectedSources, true)) {
                throw new RuntimeException('Only extra charges can be deleted here. Room charges and payments are protected.');
            }

            $folioId = (int)$tx['folio_id'];

            $pdo->beginTransaction();

            // Prefer a soft void (keeps an audit trail and is reflected in totals,
            // which already ignore is_voided rows). Fall back to a hard delete only
            // if the column genuinely doesn't exist.
            if (extras_db_column_exists($pdo, 'folio_transactions', 'is_voided')) {
                $sets = ['is_voided = 1'];
                $params = [':tx' => $transactionId, ':order_id' => $orderId];
                if (extras_db_column_exists($pdo, 'folio_transactions', 'voided_at')) {
                    $sets[] = 'voided_at = NOW()';
                }
                if (extras_db_column_exists($pdo, 'folio_transactions', 'voided_by')) {
                    $sets[] = 'voided_by = :voided_by';
                    $params[':voided_by'] = extras_current_user_id();
                }
                if (extras_db_column_exists($pdo, 'folio_transactions', 'void_reason')) {
                    $sets[] = "void_reason = :void_reason";
                    $params[':void_reason'] = 'Deleted extra (mistake/duplicate)';
                }
                $pdo->prepare("UPDATE folio_transactions SET " . implode(', ', $sets) . " WHERE id = :tx AND order_id = :order_id")
                    ->execute($params);
            } else {
                // No void column - remove the row outright.
                $pdo->prepare("DELETE FROM folio_transactions WHERE id = :tx AND order_id = :order_id")
                    ->execute([':tx' => $transactionId, ':order_id' => $orderId]);
            }

            // Detach any linked extra_charge_items so they don't dangle.
            if (extras_table_exists($pdo, 'extra_charge_items')) {
                $pdo->prepare("DELETE FROM extra_charge_items WHERE folio_transaction_id = :tx AND order_id = :order_id")
                    ->execute([':tx' => $transactionId, ':order_id' => $orderId]);
            }

            if (extras_table_exists($pdo, 'booking_extra_agent_payers')) {
                $payerDeleteWhere = [];
                $payerDeleteParams = [':payer_delete_order_id' => $orderId];
                if (extras_db_column_exists($pdo, 'booking_extra_agent_payers', 'transaction_id')) {
                    $payerDeleteWhere[] = 'transaction_id = :payer_delete_transaction_id';
                    $payerDeleteParams[':payer_delete_transaction_id'] = $transactionId;
                }
                if (extras_db_column_exists($pdo, 'booking_extra_agent_payers', 'folio_transaction_id')) {
                    $payerDeleteWhere[] = 'folio_transaction_id = :payer_delete_folio_transaction_id';
                    $payerDeleteParams[':payer_delete_folio_transaction_id'] = $transactionId;
                }
                if ($payerDeleteWhere) {
                    $pdo->prepare('DELETE FROM booking_extra_agent_payers WHERE order_id = :payer_delete_order_id AND (' . implode(' OR ', $payerDeleteWhere) . ')')
                        ->execute($payerDeleteParams);
                }
            }

            // Keep folio totals in step (these already ignore voided rows).
            if ($folioId > 0) {
                recalculate_folio_totals($pdo, $folioId);
            }
            recalculate_order_folio_totals($pdo, $orderId);

            $pdo->commit();
            header('Location: extras.php?order_id=' . $orderId . '&extra_deleted=1');
            exit;
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            $errors[] = $e->getMessage();
        }
    }
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_extra'])) {
    $orderId = (int)($_POST['order_id'] ?? 0);
    $txDate = trim((string)($_POST['tx_date'] ?? date('Y-m-d')));
    $postTo = trim((string)($_POST['post_to'] ?? 'master'));
    $otherAgentId = (int)($_POST['other_agent_id'] ?? 0);

    $categoryIds = $_POST['category_id'] ?? [];
    $serviceIds = $_POST['service_id'] ?? [];
    $quantities = $_POST['quantity'] ?? [];
    $postedUnitPrices = $_POST['unit_price'] ?? [];

    if (!is_array($categoryIds)) $categoryIds = [$categoryIds];
    if (!is_array($serviceIds)) $serviceIds = [$serviceIds];
    if (!is_array($quantities)) $quantities = [$quantities];
    if (!is_array($postedUnitPrices)) $postedUnitPrices = [$postedUnitPrices];

    $extraLines = [];
    $needsAuditTable = true;

    if ($orderId <= 0) {
        $errors[] = 'Invalid booking selected.';
    }
    if ($txDate === '') {
        $errors[] = 'Please enter charge date.';
    }
    if ($postTo === 'other' && $otherAgentId <= 0) {
        $errors[] = 'Please select the agent responsible for paying these extras.';
    }

    $lineCount = max(count($categoryIds), count($serviceIds), count($quantities), count($postedUnitPrices));
    if ($lineCount <= 0) {
        $errors[] = 'Please add at least one extra line.';
    }

    if (!$errors) {
        $categoryNameById = [];
        foreach ($extraCategories as $catRow) {
            $categoryNameById[(int)$catRow['id']] = trim((string)$catRow['category']);
        }

        for ($i = 0; $i < $lineCount; $i++) {
            $lineNo = $i + 1;
            $categoryId = (int)($categoryIds[$i] ?? 0);
            $serviceValue = trim((string)($serviceIds[$i] ?? ''));
            $quantity = (float)($quantities[$i] ?? 0);
            $postedUnitPrice = (float)($postedUnitPrices[$i] ?? 0);

            if ($categoryId <= 0 && $serviceValue === '' && abs($quantity) <= 0.00001 && abs($postedUnitPrice) <= 0.00001) {
                continue;
            }

            if ($categoryId <= 0) {
                $errors[] = 'Extra line ' . $lineNo . ': please select an extra category.';
                continue;
            }
            if ($serviceValue === '') {
                $errors[] = 'Extra line ' . $lineNo . ': please select an extra/service item.';
                continue;
            }

            $selectedItem = null;
            foreach (($extraItemOptions[$categoryId] ?? []) as $opt) {
                if ((string)($opt['value'] ?? '') === $serviceValue) {
                    $selectedItem = $opt;
                    break;
                }
            }

            if (!$selectedItem) {
                $errors[] = 'Extra line ' . $lineNo . ': selected item is not valid for the selected category.';
                continue;
            }

            $categoryLabel = $categoryNameById[$categoryId] ?? 'Extra';
            if ($categoryLabel === '') $categoryLabel = 'Extra';

            $serviceCategoryName = strtolower(trim($categoryLabel));
            $isAdjustmentCategory = ($serviceCategoryName === 'adjustment' || strpos($serviceCategoryName, 'adjustment') !== false);

            $description = trim((string)($selectedItem['label'] ?? 'Extra Item'));
            $standardUnitPrice = (float)($selectedItem['price'] ?? 0);
            $unitPrice = abs($postedUnitPrice) > 0.00001 ? $postedUnitPrice : $standardUnitPrice;
            $standardAmount = round($standardUnitPrice * $quantity, 2);
            $amount = round($unitPrice * $quantity, 2);
            $isManualOverride = abs($unitPrice - $standardUnitPrice) > 0.00001;

            if ($isAdjustmentCategory) {
                if (abs($quantity) <= 0.00001) $errors[] = 'Extra line ' . $lineNo . ': quantity cannot be zero for an adjustment.';
                if (abs($unitPrice) <= 0.00001) $errors[] = 'Extra line ' . $lineNo . ': unit price cannot be zero for an adjustment.';
                if (abs($amount) <= 0.00001) $errors[] = 'Extra line ' . $lineNo . ': adjustment amount cannot be zero.';
            } else {
                if ($quantity <= 0) $errors[] = 'Extra line ' . $lineNo . ': quantity must be greater than zero.';
                if ($unitPrice <= 0) $errors[] = 'Extra line ' . $lineNo . ': unit price must be greater than zero.';
                if ($amount <= 0) $errors[] = 'Extra line ' . $lineNo . ': charge amount must be greater than zero.';
            }

            $txType = strtolower(preg_replace('/[^a-zA-Z0-9]+/', '_', $categoryLabel));
            $txType = trim($txType, '_');
            if ($txType === '') $txType = 'extra';

            if ($isManualOverride) $needsAuditTable = true;

            $extraLines[] = [
                'line_no' => $lineNo,
                'category_id' => $categoryId,
                'service_id' => (int)($selectedItem['source_id'] ?? 0),
                'service_value' => $serviceValue,
                'source_table' => (string)($selectedItem['source'] ?? 'tblservices'),
                'source_id' => (int)($selectedItem['source_id'] ?? 0),
                'item_type' => (string)($selectedItem['item_type'] ?? 'Extra'),
                'item_name' => (string)($selectedItem['item_name'] ?? $description),
                'item_code' => (string)($selectedItem['item_code'] ?? ''),
                'source_payload' => $selectedItem,
                'category_label' => $categoryLabel,
                'description' => $description,
                'standard_unit_price' => $standardUnitPrice,
                'unit_price' => $unitPrice,
                'quantity' => $quantity,
                'standard_amount' => $standardAmount,
                'amount' => $amount,
                'is_manual_override' => $isManualOverride,
                'tx_type' => $txType,
            ];
        }
    }

    if (!$errors && empty($extraLines)) {
        $errors[] = 'Please add at least one valid extra line.';
    }

    if (!$errors && $needsAuditTable) {
        try {
            ensure_extra_charge_audit_table($pdo);
        } catch (Throwable $e) {
            $errors[] = 'Could not prepare extra charge audit log: ' . $e->getMessage();
        }
    }

    if (!$errors) {
        try {
            ensure_extra_charge_item_link_table($pdo);
        } catch (Throwable $e) {
            $errors[] = 'Could not prepare extra charge item link table: ' . $e->getMessage();
        }
    }

    if (!$errors) {
        try {
            $stmtOrder = $pdo->prepare("
                SELECT property_id
                FROM orders
                WHERE id = :id
                LIMIT 1
            ");
            $stmtOrder->execute([':id' => $orderId]);
            $bookingPropertyId = (int)($stmtOrder->fetchColumn() ?: 0);

            if ($bookingPropertyId <= 0) {
                throw new RuntimeException('Booking property not found.');
            }

            $pdo->beginTransaction();

            // Safety net for old bookings: make sure accommodation charges exist
            // before Extras update the booking totals. New bookings are already
            // handled by reservation_form.php, but this prevents stale totals.
            ensure_room_charges_posted_to_master_folio($pdo, $orderId);

            $guestFolioTargetId = get_guest_folio_id_from_post_value($postTo);

            if ($postTo === 'master') {
                $folioId = get_or_create_master_folio($pdo, $orderId);
            } elseif ($postTo === 'other') {
                if ($otherAgentId <= 0) {
                    throw new RuntimeException('Please select the agent responsible for paying these extras.');
                }
                ensure_booking_extra_agent_payers_table($pdo);
                $folioId = get_or_create_other_agent_folio($pdo, $orderId, $bookingPropertyId, $otherAgentId);
            } elseif ($guestFolioTargetId > 0) {
                $folioId = validate_guest_folio_target($pdo, $orderId, $guestFolioTargetId);
            } else {
                $rooming = json_decode($postTo, true);
                if (!is_array($rooming)) {
                    throw new RuntimeException('Invalid room or guest folio selection.');
                }
                $folioId = get_or_create_room_folio($pdo, $orderId, $rooming);
            }

            $folioTxHasPropertyId = extras_db_column_exists($pdo, 'folio_transactions', 'property_id');
            if ($folioTxHasPropertyId) {
                $stmtInsert = $pdo->prepare("
                    INSERT INTO folio_transactions
                    (property_id, folio_id, order_id, tx_date, tx_type, description, debit, credit, source_table, source_id)
                    VALUES
                    (:property_id, :folio_id, :order_id, :tx_date, :tx_type, :description, :debit, 0, :source_table, :source_id)
                ");
            } else {
                $stmtInsert = $pdo->prepare("
                    INSERT INTO folio_transactions
                    (folio_id, order_id, tx_date, tx_type, description, debit, credit, source_table, source_id)
                    VALUES
                    (:folio_id, :order_id, :tx_date, :tx_type, :description, :debit, 0, :source_table, :source_id)
                ");
            }

            $stmtAudit = null;
            if ($needsAuditTable) {
                $stmtAudit = $pdo->prepare("
                    INSERT INTO extra_charge_audit_log
                    (order_id, folio_id, folio_transaction_id, service_id, action_type,
                     standard_unit_price, override_unit_price, quantity, standard_amount, override_amount,
                     reason, payload_json, action_by)
                    VALUES
                    (:order_id, :folio_id, :folio_transaction_id, :service_id, :action_type,
                     :standard_unit_price, :override_unit_price, :quantity, :standard_amount, :override_amount,
                     :reason, :payload_json, :action_by)
                ");
            }

            $stmtExtraItem = $pdo->prepare("\n                INSERT INTO extra_charge_items\n                (property_id, order_id, folio_id, folio_transaction_id, line_no, category_id, category_name,\n                 source_table, source_id, item_type, item_name, item_code, qty, standard_unit_price,\n                 unit_price, standard_amount, line_total, is_manual_override, description, source_payload_json, created_by)\n                VALUES\n                (:property_id, :order_id, :folio_id, :folio_transaction_id, :line_no, :category_id, :category_name,\n                 :source_table, :source_id, :item_type, :item_name, :item_code, :qty, :standard_unit_price,\n                 :unit_price, :standard_amount, :line_total, :is_manual_override, :description, :source_payload_json, :created_by)\n            ");

            foreach ($extraLines as $line) {
                $fullDescription = $line['description'] . ' x ' . money_fmt($line['quantity']) . ' @ ' . money_fmt($line['unit_price']);

                $folioTxParams = [
                    ':folio_id'    => $folioId,
                    ':order_id'    => $orderId,
                    ':tx_date'     => $txDate . ' 00:00:00',
                    ':tx_type'     => $line['tx_type'],
                    ':description' => $fullDescription,
                    ':debit'       => $line['amount'],
                    ':source_table'=> $line['source_table'],
                    ':source_id'   => $line['source_id'],
                ];
                if ($folioTxHasPropertyId) {
                    $folioTxParams[':property_id'] = $bookingPropertyId;
                }
                $stmtInsert->execute($folioTxParams);

                $folioTransactionId = (int)$pdo->lastInsertId();

                if ($postTo === 'other') {
                    extras_save_agent_payer_mapping(
                        $pdo,
                        $folioTransactionId,
                        $orderId,
                        $otherAgentId,
                        $folioId,
                        (int)($line['service_id'] ?? $line['source_id'] ?? 0)
                    );
                }

                $stmtExtraItem->execute([
                    ':property_id' => $bookingPropertyId,
                    ':order_id' => $orderId,
                    ':folio_id' => $folioId,
                    ':folio_transaction_id' => $folioTransactionId,
                    ':line_no' => (int)$line['line_no'],
                    ':category_id' => (int)$line['category_id'],
                    ':category_name' => $line['category_label'],
                    ':source_table' => $line['source_table'],
                    ':source_id' => (int)$line['source_id'],
                    ':item_type' => $line['item_type'],
                    ':item_name' => $line['item_name'] !== '' ? $line['item_name'] : $line['description'],
                    ':item_code' => trim((string)$line['item_code']) !== '' ? trim((string)$line['item_code']) : null,
                    ':qty' => $line['quantity'],
                    ':standard_unit_price' => round($line['standard_unit_price'], 2),
                    ':unit_price' => round($line['unit_price'], 2),
                    ':standard_amount' => round($line['standard_amount'], 2),
                    ':line_total' => round($line['amount'], 2),
                    ':is_manual_override' => $line['is_manual_override'] ? 1 : 0,
                    ':description' => $fullDescription,
                    ':source_payload_json' => json_encode($line['source_payload'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
                    ':created_by' => extras_current_user_id(),
                ]);


                extras_sync_extra_to_restaurant_calendar(
                    $pdo,
                    $orderId,
                    $bookingPropertyId,
                    $folioId,
                    $folioTransactionId,
                    $txDate,
                    $line,
                    $fullDescription
                );

                if ($stmtAudit) {
                    $auditPayload = json_encode([
                        'service' => $line['description'],
                        'category' => $line['category_label'],
                        'post_to' => $postTo,
                        'other_agent_id' => $postTo === 'other' ? $otherAgentId : null,
                        'tx_date' => $txDate,
                        'line_no' => $line['line_no'],
                        'standard_unit_price' => round($line['standard_unit_price'], 2),
                        'override_unit_price' => round($line['unit_price'], 2),
                        'quantity' => $line['quantity'],
                        'standard_amount' => $line['standard_amount'],
                        'override_amount' => $line['amount'],
                    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

                    $stmtAudit->execute([
                        ':order_id' => $orderId,
                        ':folio_id' => $folioId,
                        ':folio_transaction_id' => $folioTransactionId,
                        ':service_id' => $line['service_id'],
                        ':action_type' => $line['is_manual_override'] ? 'manual_unit_price_override' : 'extra_added',
                        ':standard_unit_price' => round($line['standard_unit_price'], 2),
                        ':override_unit_price' => round($line['unit_price'], 2),
                        ':quantity' => $line['quantity'],
                        ':standard_amount' => $line['standard_amount'],
                        ':override_amount' => $line['amount'],
                        ':reason' => $line['is_manual_override']
                            ? 'Unit price manually changed while posting extra charge.'
                            : 'Extra charge captured at standard price.',
                        ':payload_json' => $auditPayload,
                        ':action_by' => extras_current_user_id(),
                    ]);
                }
            }

            recalculate_order_folio_totals($pdo, $orderId);

            $pdo->commit();

            header('Location: extras.php?order_id=' . $orderId . '&saved=1&lines=' . count($extraLines));
            exit;
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            $errors[] = $e->getMessage();
        }
    }
}

if (isset($_GET['saved']) && (int)$_GET['saved'] === 1) {
    $postedLines = max(1, (int)($_GET['lines'] ?? 1));
    $success = $postedLines . ' extra / adjustment line' . ($postedLines === 1 ? '' : 's') . ' posted successfully. Manual price overrides are highlighted and audit logged.';
}
if (isset($_GET['moved']) && (int)$_GET['moved'] === 1) {
    $success = 'Folio transaction moved successfully and folio balances recalculated.';
}
if (isset($_GET['extra_deleted']) && (int)$_GET['extra_deleted'] === 1) {
    $success = 'Extra deleted and folio balances recalculated.';
}

if ($orderId > 0) {
    $stmt = $pdo->prepare("
        SELECT
            o.*,
            g.firstname,
            g.lastname,
            p.name AS property_name,
            p.code AS property_code,
            CONCAT(COALESCE(a.firstname,''), ' ') AS agent_name
        FROM orders o
        LEFT JOIN guests g ON g.id = o.guest_id
        LEFT JOIN properties p ON p.id = o.property_id
        LEFT JOIN agents a ON a.id = o.agent_id
        WHERE o.id = :id
        LIMIT 1
    ");
    $stmt->execute([':id' => $orderId]);
    $order = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$order) {
        $errors[] = 'Booking not found.';
        $orderId = 0;
    } else {
        $selectedPropertyId = (int)($order['property_id'] ?? 0);

        try {
            $masterFolioId = get_or_create_master_folio($pdo, $orderId);
            ensure_room_charges_posted_to_master_folio($pdo, $orderId);
            recalculate_order_folio_totals($pdo, $orderId);

            $stmt = $pdo->prepare("
                SELECT *
                FROM folios
                WHERE id = :id
                LIMIT 1
            ");
            $stmt->execute([':id' => $masterFolioId]);
            $folio = $stmt->fetch(PDO::FETCH_ASSOC);

            $bookingFolios = get_booking_folios_with_transactions($pdo, $orderId);

            $stmtMix = $pdo->prepare("
                SELECT rt.title, COUNT(*) AS qty
                FROM order_rooming r
                INNER JOIN room_types rt ON rt.id = r.room_type_id
                WHERE r.order_id = :id
                GROUP BY r.room_type_id, rt.title
                ORDER BY rt.title
            ");
            $stmtMix->execute([':id' => $orderId]);
            $roomMixRows = $stmtMix->fetchAll(PDO::FETCH_ASSOC);

            $roomMix = [];
            foreach ($roomMixRows as $r) {
                $roomMix[] = $r['title'] . ' x ' . $r['qty'];
            }
            $roomMixStr = implode(', ', $roomMix);
        } catch (Throwable $e) {
            $errors[] = $e->getMessage();
        }
    }
}

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

<div class="hero-band">
    <div class="row g-4 align-items-center">
        <div class="col-lg-8">
            <div class="page-eyebrow text-white-50"><i class="bi bi-bag-plus"></i> Folios & Charges</div>
            <div class="hero-title mt-2">Extras / Charges</div>
            <div class="hero-subtitle mt-2">
                Add manual folio charges such as restaurant, minibar, laundry, transfer, park fees, or adjustments by property.
            </div>
        </div>
        <div class="col-lg-4 text-lg-end">
            <?php if ($orderId > 0): ?>
                <button type="button"
                        class="btn btn-warning btn-sm me-2 extra-top-btn"
                        data-bs-toggle="modal"
                        data-bs-target="#extraChargeModal">
                    <i class="bi bi-plus-circle me-1"></i>Extra
                </button>

                <a href="/rooms/modules/payments/proforma_room_account.php?order_id=<?php echo (int)$orderId; ?>"
                   target="_blank"
                   class="btn btn-outline-light btn-sm me-2"
                   title="Room Account Proforma">
                    <i class="bi bi-door-open me-1"></i>Room PF
                </a>

                <a href="/rooms/modules/payments/proforma_master_folio.php?order_id=<?php echo (int)$orderId; ?>"
                   target="_blank"
                   class="btn btn-outline-light btn-sm me-2"
                   title="Master Folio Proforma">
                    <i class="bi bi-collection me-1"></i>Master PF
                </a>
            <?php endif; ?>
            <a href="/rooms/modules/reservations/reservation_list.php<?php echo $selectedPropertyId > 0 ? '?property_id=' . (int)$selectedPropertyId : ''; ?>" class="btn btn-outline-light btn-sm me-2">
                <i class="bi bi-arrow-left me-1"></i>Reservations
            </a>
            <?php if ($orderId > 0): ?>
                <a href="/rooms/modules/payments/payments.php?order_id=<?php echo (int)$orderId; ?>" class="btn btn-light btn-sm">
                    <i class="bi bi-cash-stack me-1"></i>Payments
                </a>
            <?php endif; ?>
        </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="card panel-card mb-4">
    <div class="card-body">
        <form method="get" class="row g-3 align-items-end">
            <div class="col-md-3">
                <label class="form-label">Property</label>
                <select name="property_id" class="form-select">
                    <option value="0">All properties</option>
                    <?php foreach ($properties as $property): ?>
                        <option value="<?php echo (int)$property['id']; ?>" <?php echo $selectedPropertyId === (int)$property['id'] ? 'selected' : ''; ?>>
                            <?php echo h($property['name']); ?> - <?php echo h($property['code']); ?>
                        </option>
                    <?php endforeach; ?>
                </select>
            </div>

<div class="col-md-3">
    <label class="form-label">Booking ID</label>
    <input type="number" name="order_id" class="form-control" value="<?php echo (int)$orderId; ?>" placeholder="Enter booking ID">
</div>

<div class="col-md-4">
    <label class="form-label">&nbsp;</label>
    <div class="d-flex gap-2 flex-wrap">
        <?php if ($orderId > 0): ?>
            <button type="button"
                    class="btn btn-warning btn-sm"
                    data-bs-toggle="modal"
                    data-bs-target="#extraChargeModal"
                    title="Post Extra Charge">
                <i class="bi bi-plus-circle me-1"></i>Extra
            </button>

            <a href="/rooms/modules/payments/proforma_room_account.php?order_id=<?php echo (int)$orderId; ?>"
               target="_blank"
               class="btn btn-outline-primary btn-sm"
               title="Room Account Proforma">
                <i class="bi bi-door-open me-1"></i>Room PF
            </a>

            <a href="/rooms/modules/payments/proforma_master_folio.php?order_id=<?php echo (int)$orderId; ?>"
               target="_blank"
               class="btn btn-outline-dark btn-sm"
               title="Master Folio Proforma">
                <i class="bi bi-collection me-1"></i>Master PF
            </a>
        <?php endif; ?>
    </div>
</div>

<div class="col-md-2 d-grid">
    <button class="btn btn-primary">Load Booking</button>
</div>
        </form>
    </div>
</div>

<?php if ($order): ?>
    <?php
    $guestName = trim(($order['firstname'] ?? '') . ' ' . ($order['lastname'] ?? ''));
    if ($guestName === '') {
        $guestName = 'Guest';
    }

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

    $propertyName = trim((string)($order['property_name'] ?? ''));
    if ($propertyName === '') {
        $propertyName = 'Not set';
    }

    $propertyLabel = $propertyName;
    if (!empty($order['property_code'])) {
        $propertyLabel .= ' (' . $order['property_code'] . ')';
    }

    $roomFolios = $orderId ? get_room_folios($pdo, $orderId) : [];
    $guestFolios = $orderId ? get_guest_folios_for_order($pdo, $orderId) : [];
    $moveTargetFolios = [];
    foreach ($bookingFolios as $moveFolioRow) {
        $moveTargetFolios[] = [
            'id' => (int)($moveFolioRow['id'] ?? 0),
            'label' => extras_folio_label($moveFolioRow),
            'balance' => (float)($moveFolioRow['balance'] ?? 0),
        ];
    }
    ?>
    <div class="row g-4">
        <div class="col-xl-4">
            <div class="form-section">
                <div class="form-section-title">
                    <i class="bi bi-journal-text"></i>
                    Booking & Folio Summary
                </div>

                <div class="mini-stat mb-3"><span>Property</span><strong><?php echo h($propertyLabel); ?></strong></div>
                <div class="mini-stat mb-3"><span>Booking</span><strong><?php echo h($order['order_no'] ?: ('#' . $order['id'])); ?></strong></div>
                <div class="mini-stat mb-3"><span>Guest</span><strong><?php echo h($guestName); ?></strong></div>
                <div class="mini-stat mb-3"><span>Agent</span><strong><?php echo h($agentName); ?></strong></div>
                <div class="mini-stat mb-3"><span>Stay</span><strong><?php echo h($order['check_in']); ?> to <?php echo h($order['check_out']); ?></strong></div>
                <div class="mini-stat mb-3"><span>Room Mix</span><strong><?php echo h($roomMixStr !== '' ? $roomMixStr : 'Not set'); ?></strong></div>

                <?php if ($folio): ?>
                    <div class="mini-stat mb-3"><span>Master Folio No</span><strong><?php echo h($folio['folio_no']); ?></strong></div>
                <?php endif; ?>

                <?php
                $grandCharges = 0;
                $grandPayments = 0;
                $grandBalance = 0;
                foreach ($bookingFolios as $bfSum) {
                    $grandCharges += (float)($bfSum['total_charges'] ?? 0);
                    $grandPayments += (float)($bfSum['total_payments'] ?? 0);
                    $grandBalance += (float)($bfSum['balance'] ?? 0);
                }
                ?>
                <div class="mini-stat mb-3"><span>Total Charges</span><strong><?php echo money_fmt($grandCharges); ?></strong></div>
                <div class="mini-stat mb-3"><span>Total Payments</span><strong><?php echo money_fmt($grandPayments); ?></strong></div>
                <div class="mini-stat mb-3"><span>Total Balance</span><strong><?php echo money_fmt($grandBalance); ?></strong></div>
            </div>
        </div>

        <div class="col-xl-8">
            <div class="card panel-card">
                <div class="card-header">
                    <h5 class="mb-0">Booking Folios & Transactions</h5>
                </div>
                <div class="card-body">
                    <?php if (empty($bookingFolios)): ?>
                        <div class="text-center text-muted py-4">No folios found for this booking.</div>
                    <?php else: ?>
                        <div class="accordion" id="folioAccordion">
                            <?php foreach ($bookingFolios as $index => $bf): ?>
                                <?php
                                $collapseId = 'folioCollapse' . (int)$bf['id'];
                                $headingId = 'folioHeading' . (int)$bf['id'];
                                $isOpen = $index === 0 ? 'show' : '';
                                $btnState = $index === 0 ? '' : 'collapsed';
                                ?>
                                <div class="accordion-item mb-3 border rounded-3 overflow-hidden">
                                    <h2 class="accordion-header" id="<?php echo $headingId; ?>">
                                        <button class="accordion-button <?php echo $btnState; ?>" type="button"
                                                data-bs-toggle="collapse"
                                                data-bs-target="#<?php echo $collapseId; ?>"
                                                aria-expanded="<?php echo $index === 0 ? 'true' : 'false'; ?>"
                                                aria-controls="<?php echo $collapseId; ?>">
                                            <div class="w-100 d-flex justify-content-between align-items-center pe-3">
                                                <div>
                                                    <strong><?php echo h($bf['folio_name']); ?></strong>
                                                    <div class="small text-muted">
                                                        <?php echo h($bf['folio_label']); ?>
                                                        <?php if (!empty($bf['room_no'])): ?>
                                                            | Room <?php echo h($bf['room_no']); ?>
                                                        <?php endif; ?>
                                                    </div>
                                                </div>
                                                <div class="text-end">
                                                    <div class="small text-muted">Balance</div>
                                                    <strong><?php echo money_fmt($bf['balance'] ?? 0); ?></strong>
                                                </div>
                                            </div>
                                        </button>
                                    </h2>

                                    <div id="<?php echo $collapseId; ?>"
                                         class="accordion-collapse collapse <?php echo $isOpen; ?>"
                                         aria-labelledby="<?php echo $headingId; ?>"
                                         data-bs-parent="#folioAccordion">
                                        <div class="accordion-body p-0">
                                            <div class="p-3 border-bottom bg-light">
                                                <div class="row g-3">
                                                    <div class="col-md-4">
                                                        <div class="mini-stat">
                                                            <span>Total Charges</span>
                                                            <strong><?php echo money_fmt($bf['total_charges'] ?? 0); ?></strong>
                                                        </div>
                                                    </div>
                                                    <div class="col-md-4">
                                                        <div class="mini-stat">
                                                            <span>Total Payments</span>
                                                            <strong><?php echo money_fmt($bf['total_payments'] ?? 0); ?></strong>
                                                        </div>
                                                    </div>
                                                    <div class="col-md-4">
                                                        <div class="mini-stat">
                                                            <span>Balance</span>
                                                            <strong><?php echo money_fmt($bf['balance'] ?? 0); ?></strong>
                                                        </div>
                                                    </div>
                                                </div>
                                            </div>

                                            <div class="table-responsive">
                                                <table class="table table-modern align-middle mb-0">
                                                    <thead>
                                                        <tr>
                                                            <th>Date</th>
                                                            <th>Type</th>
                                                            <th>Description</th>
                                                            <th class="text-end">Debit</th>
                                                            <th class="text-end">Credit</th>
                                                            <th class="text-end">Action</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <?php if (empty($bf['transactions'])): ?>
                                                            <tr>
                                                                <td colspan="6" class="text-center text-muted py-4">
                                                                    No transactions in this folio.
                                                                </td>
                                                            </tr>
                                                        <?php else: ?>
                                                            <?php foreach ($bf['transactions'] as $tx): ?>
                                                                <tr>
                                                                    <td><?php echo h($tx['tx_date']); ?></td>
                                                                    <td><?php echo h($tx['tx_type']); ?></td>
                                                                    <td>
                                                                        <?php echo h($tx['description']); ?>
                                                                        <?php if (!empty($tx['extra_items'])): ?>
                                                                            <div class="extra-linked-items mt-1">
                                                                                <?php foreach ($tx['extra_items'] as $linkedItem): ?>
                                                                                    <div class="extra-linked-item">
                                                                                        <span class="badge text-bg-light border me-1"><?php echo h($linkedItem['item_type'] ?: 'Item'); ?></span>
                                                                                        <strong><?php echo h($linkedItem['item_name']); ?></strong>
                                                                                        <?php if (!empty($linkedItem['item_code'])): ?><span class="text-muted">[<?php echo h($linkedItem['item_code']); ?>]</span><?php endif; ?>
                                                                                        <span class="text-muted"> • <?php echo h($linkedItem['source_table']); ?> #<?php echo (int)$linkedItem['source_id']; ?></span>
                                                                                        <span class="text-muted"> • Qty <?php echo money_fmt($linkedItem['qty']); ?> @ <?php echo money_fmt($linkedItem['unit_price']); ?></span>
                                                                                        <span class="fw-bold"> = <?php echo money_fmt($linkedItem['line_total']); ?></span>
                                                                                    </div>
                                                                                <?php endforeach; ?>
                                                                            </div>
                                                                        <?php endif; ?>
                                                                    </td>
                                                                    <td class="text-end"><?php echo money_fmt($tx['debit']); ?></td>
                                                                    <td class="text-end"><?php echo money_fmt($tx['credit']); ?></td>
                                                                    <td class="text-end">
                                                                        <?php
                                                                            $txTypeLower = strtolower((string)($tx['tx_type'] ?? ''));
                                                                            $txSrcLower  = strtolower((string)($tx['source_table'] ?? ''));
                                                                            $isProtectedRow = in_array($txTypeLower, [
                                                                                'room_charge','accommodation',
                                                                                'payment','receipt','deposit','payment_received','folio_payment',
                                                                                'guest_payment','payment_void','void_payment','payment_reversal',
                                                                                'reversal','refund','payment_refund',
                                                                            ], true) || in_array($txSrcLower, ['rel_orders_prices','payment','payments','receipt','receipts'], true);
                                                                            $isVoidedRow = !empty($tx['is_voided']);
                                                                        ?>
                                                                        <button type="button"
                                                                                class="btn btn-sm btn-outline-primary move-tx-btn"
                                                                                data-bs-toggle="modal"
                                                                                data-bs-target="#moveFolioTxModal"
                                                                                data-tx-id="<?php echo (int)$tx['id']; ?>"
                                                                                data-current-folio-id="<?php echo (int)$bf['id']; ?>"
                                                                                data-description="<?php echo h($tx['description']); ?>"
                                                                                data-type="<?php echo h($tx['tx_type']); ?>"
                                                                                data-debit="<?php echo h(money_fmt($tx['debit'])); ?>"
                                                                                data-credit="<?php echo h(money_fmt($tx['credit'])); ?>">
                                                                            <i class="bi bi-arrow-left-right me-1"></i>Move
                                                                        </button>
                                                                        <?php if (!$isProtectedRow && !$isVoidedRow): ?>
                                                                            <form method="post" action="extras.php" class="d-inline delete-extra-form"
                                                                                  onsubmit="return confirm('Delete this extra? This removes the charge from the folio. Room charges and payments are not affected.');">
                                                                                <input type="hidden" name="delete_extra_transaction" value="1">
                                                                                <input type="hidden" name="order_id" value="<?php echo (int)$order['id']; ?>">
                                                                                <input type="hidden" name="folio_transaction_id" value="<?php echo (int)$tx['id']; ?>">
                                                                                <button type="submit" class="btn btn-sm btn-outline-danger" title="Delete this extra">
                                                                                    <i class="bi bi-trash me-1"></i>Delete
                                                                                </button>
                                                                            </form>
                                                                        <?php endif; ?>
                                                                    </td>
                                                                </tr>
                                                            <?php endforeach; ?>
                                                        <?php endif; ?>
                                                    </tbody>
                                                </table>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            <?php endforeach; ?>
                        </div>
                    <?php endif; ?>
                </div>
            </div>
        </div>
    </div>
<?php endif; ?>



<?php if ($order && !empty($moveTargetFolios)): ?>
<div class="modal fade" id="moveFolioTxModal" tabindex="-1" aria-labelledby="moveFolioTxModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-lg modal-dialog-centered">
        <form method="post" class="modal-content">
            <div class="modal-header">
                <div>
                    <h5 class="modal-title" id="moveFolioTxModalLabel">
                        <i class="bi bi-arrow-left-right me-2"></i>Move Folio Transaction
                    </h5>
                    <div class="small text-muted">Move a charge, adjustment, or payment row to the correct folio.</div>
                </div>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>

            <div class="modal-body">
                <input type="hidden" name="order_id" value="<?php echo (int)$order['id']; ?>">
                <input type="hidden" name="folio_transaction_id" id="move_folio_transaction_id" value="">

                <div class="alert alert-light border small">
                    <div><strong>Transaction:</strong> <span id="move_tx_description">Select transaction</span></div>
                    <div class="mt-1">
                        <span class="badge text-bg-light border" id="move_tx_type">Type</span>
                        <span class="ms-2">Debit: <strong id="move_tx_debit">0.00</strong></span>
                        <span class="ms-2">Credit: <strong id="move_tx_credit">0.00</strong></span>
                    </div>
                </div>

                <div class="mb-3">
                    <label class="form-label">Move To Folio</label>
                    <select name="target_folio_id" id="move_target_folio_id" class="form-select" required>
                        <option value="">Select destination folio</option>
                        <?php foreach ($moveTargetFolios as $targetFolio): ?>
                            <?php if ((int)$targetFolio['id'] <= 0) continue; ?>
                            <option value="<?php echo (int)$targetFolio['id']; ?>">
                                <?php echo h($targetFolio['label']); ?> | Balance <?php echo money_fmt($targetFolio['balance']); ?>
                            </option>
                        <?php endforeach; ?>
                    </select>
                    <div class="form-text">The transaction will be removed from the old folio and attached to this folio. Both balances will recalculate automatically.</div>
                </div>

                <div class="mb-0">
                    <label class="form-label">Reason / Note</label>
                    <textarea name="move_reason" class="form-control" rows="3" placeholder="Example: Posted to room folio by mistake; should be on guest folio."></textarea>
                </div>
            </div>

            <div class="modal-footer">
                <button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
                <button type="submit" name="move_folio_transaction" value="1" class="btn btn-primary">
                    <i class="bi bi-check-circle me-2"></i>Move Transaction
                </button>
            </div>
        </form>
    </div>
</div>
<?php endif; ?>

<style>
.extra-line-card {
    border: 1px solid #e5e7eb;
    border-radius: 16px;
    background: #fff;
    box-shadow: 0 8px 22px rgba(15, 23, 42, .06);
    padding: 14px;
    margin-bottom: 12px;
}
.extra-line-card .line-title {
    font-size: 13px;
    letter-spacing: .02em;
    text-transform: uppercase;
    color: #64748b;
    font-weight: 800;
}
.extra-line-card .extra-unit-price.is-price-overridden {
    border-color: #dc3545 !important;
    background: #fff5f5 !important;
    color: #b02a37 !important;
    font-weight: 800;
    box-shadow: 0 0 0 .18rem rgba(220, 53, 69, .12) !important;
}
.override-note {
    color: #b02a37 !important;
    font-weight: 700;
}
.extra-lines-summary {
    border: 1px solid #dbeafe;
    background: #eff6ff;
    border-radius: 14px;
    padding: 12px 14px;
}
.extra-lines-summary strong {
    font-size: 18px;
}

.extra-linked-items {
    border-left: 3px solid #dbeafe;
    padding-left: 8px;
}
.extra-linked-item {
    font-size: .76rem;
    line-height: 1.35;
    color: #334155;
}

.extra-picker-wrap{position:relative}
.extra-picker-wrap .extra-service-search{padding-right:2.1rem}
.extra-picker-clear{position:absolute;right:.45rem;top:50%;transform:translateY(-50%);border:0;background:transparent;color:#94a3b8;font-size:1.15rem;line-height:1;display:none}
.extra-picker-wrap.has-value .extra-picker-clear{display:block}
.extra-picker-menu{position:absolute;left:0;right:0;top:calc(100% + 4px);z-index:1065;background:#fff;border:1px solid #dbe3ef;border-radius:14px;box-shadow:0 18px 45px rgba(15,23,42,.18);max-height:280px;overflow:auto;padding:6px}
.extra-picker-option{width:100%;border:0;background:#fff;text-align:left;border-radius:10px;padding:8px 10px;display:flex;justify-content:space-between;gap:10px;align-items:flex-start;color:#0f172a}
.extra-picker-option:hover,.extra-picker-option.is-active{background:#eff6ff}
.extra-picker-option .extra-picker-label{font-weight:800;font-size:.84rem;line-height:1.2}
.extra-picker-option .extra-picker-meta{display:block;font-size:.68rem;color:#64748b;margin-top:2px}
.extra-picker-option .extra-picker-price{font-weight:900;white-space:nowrap;color:#0f172a}
.extra-picker-empty{padding:10px;border-radius:10px;background:#f8fafc;color:#64748b;font-size:.82rem}
.extra-line-card.is-auto-related{border-color:#f59e0b!important;background:#fffbeb!important}
.extra-line-card.is-auto-related .line-title{color:#92400e}


.extras-agent-search-wrap{position:relative}
.extras-agent-clear{position:absolute;right:.65rem;top:50%;transform:translateY(-50%);border:0;background:transparent;font-size:1.35rem;line-height:1;color:#6c757d;z-index:3}
.extras-agent-results-portal{position:fixed;display:none;z-index:2147483647;background:#fff;border:1px solid rgba(0,0,0,.18);border-radius:.5rem;box-shadow:0 .75rem 2rem rgba(0,0,0,.22);max-height:320px;overflow-y:auto;overscroll-behavior:contain}
.extras-agent-results-portal.is-open{display:block}
.extras-agent-result{display:block;width:100%;padding:.62rem .8rem;border:0;border-bottom:1px solid #edf0f2;background:#fff;text-align:left;cursor:pointer;color:#212529}
.extras-agent-result:last-child{border-bottom:0}
.extras-agent-result:hover,.extras-agent-result.active{background:#eef5ff}
.extras-agent-result small{display:block;color:#6c757d;margin-top:.1rem}
.extras-agent-empty{padding:.8rem;color:#6c757d}

</style>

<?php if ($order): ?>
<div class="modal fade" id="extraChargeModal" tabindex="-1" aria-labelledby="extraChargeModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-xl modal-dialog-scrollable">
        <form method="post" class="modal-content" id="extraChargeForm">
            <div class="modal-header">
                <div>
                    <h5 class="modal-title" id="extraChargeModalLabel">
                        <i class="bi bi-bag-plus me-2"></i>Post Extra Charges
                    </h5>
                    <div class="small text-muted">Add one or more extras, then post them all at once.</div>
                </div>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>

            <div class="modal-body">
                <input type="hidden" name="order_id" value="<?php echo (int)$order['id']; ?>">
                <input type="hidden" name="property_id" value="<?php echo (int)$order['property_id']; ?>">

                <div class="row g-3 mb-3">
                    <div class="col-md-4">
                        <label class="form-label">Charge Date</label>
                        <input type="date" name="tx_date" class="form-control" value="<?php echo h(date('Y-m-d')); ?>" required>
                    </div>

                    <div class="col-md-8">
                        <label class="form-label">Post To</label>
                        <select name="post_to" id="post_to" class="form-select">
                            <option value="master">Master Folio (Agent / Master Account)</option>
                            <option value="other">Others (Select another paying agent)</option>

                            <?php if (!empty($roomFolios)): ?>
                                <optgroup label="Room Folios">
                                    <?php foreach ($roomFolios as $r): ?>
                                        <option value='<?php echo h(json_encode($r, JSON_HEX_APOS | JSON_HEX_QUOT)); ?>'>
                                            Room <?php echo h($r['room_no'] ?: 'N/A'); ?>
                                            <?php if (!empty($r['room_type'])): ?> • <?php echo h($r['room_type']); ?><?php endif; ?>
                                            - <?php echo h(trim((string)$r['guest']) !== '' ? $r['guest'] : 'Guest'); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </optgroup>
                            <?php endif; ?>

                            <?php if (!empty($guestFolios)): ?>
                                <optgroup label="Individual Guest Folios">
                                    <?php foreach ($guestFolios as $gf): ?>
                                        <?php
                                            $gfRoom = trim((string)($gf['room_no'] ?? ''));
                                            $gfName = trim((string)($gf['occupant_name'] ?? ''));
                                            if ($gfName === '') {
                                                $gfName = trim((string)($gf['folio_name'] ?? 'Guest Folio'));
                                            }
                                        ?>
                                        <option value="guest:<?php echo (int)$gf['folio_id']; ?>">
                                            Guest: <?php echo h($gfName); ?>
                                            <?php if ($gfRoom !== ''): ?> • Room <?php echo h($gfRoom); ?><?php endif; ?>
                                            <?php if (!empty($gf['room_type'])): ?> • <?php echo h($gf['room_type']); ?><?php endif; ?>
                                            (<?php echo h($gf['folio_no']); ?>)
                                        </option>
                                    <?php endforeach; ?>
                                </optgroup>
                            <?php endif; ?>
                        </select>
                        <div id="otherAgentWrap" class="mt-3 d-none">
                            <label class="form-label" for="other_agent_search">Paying Agent</label>
                            <div class="extras-agent-search-wrap">
                                <input type="text" id="other_agent_search" class="form-control"
                                       placeholder="Type agent or company name..." autocomplete="off">
                                <input type="hidden" name="other_agent_id" id="other_agent_id" value="">
                                <button type="button" id="clearOtherAgent" class="extras-agent-clear d-none" aria-label="Clear paying agent">&times;</button>
                            </div>
                            <div id="otherAgentSelected" class="small text-success mt-1 d-none"></div>
                            <div class="form-text">Search by company, registered name, contact name or email. This does not replace the booking agent.</div>
                        </div>
                        <?php if (empty($guestFolios)): ?>
                            <div class="form-text">Individual guest folios appear here after room check-in occupants are saved.</div>
                        <?php endif; ?>
                    </div>
                </div>

                <div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-2">
                    <div>
                        <div class="fw-bold"><i class="bi bi-list-check me-2"></i>Extra Lines</div>
                        <div class="small text-muted">Each line creates a separate folio transaction. Services with linked park/additional fees automatically add their related service lines; all prices remain editable before saving.</div>
                    </div>
                    <button type="button" class="btn btn-sm btn-outline-primary" id="addExtraLineBtn">
                        <i class="bi bi-plus-circle me-1"></i>Add Line
                    </button>
                </div>

                <div id="extraLinesContainer"></div>

                <div class="extra-lines-summary d-flex justify-content-between align-items-center flex-wrap gap-3 mt-3">
                    <div>
                        <span class="text-muted small d-block">Lines</span>
                        <strong id="extraLineCount">0</strong>
                    </div>
                    <div class="text-end">
                        <span class="text-muted small d-block">Total Extras</span>
                        <strong id="extraGrandTotal">0.00</strong>
                    </div>
                </div>

                <div class="alert alert-info mt-3 mb-0">
                    Amount is calculated automatically as <strong>unit price × quantity</strong>. For <strong>Adjustment</strong> categories, negative quantities are allowed so you can post credits/write-backs. Normal extra categories remain positive-only.
                </div>
            </div>

            <div class="modal-footer">
                <button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
                <button type="submit" name="save_extra" value="1" class="btn btn-primary">
                    <i class="bi bi-save me-2"></i>Post All Extras
                </button>
            </div>
        </form>
    </div>
</div>

<template id="extraLineTemplate">
    <div class="extra-line-card extra-line-row">
        <div class="d-flex justify-content-between align-items-center mb-2">
            <div class="line-title">Line <span class="extra-line-number">1</span> <span class="badge text-bg-warning extra-related-badge d-none ms-2">Linked fee / related item</span></div>
            <button type="button" class="btn btn-sm btn-outline-danger remove-extra-line-btn">
                <i class="bi bi-x-lg"></i>
            </button>
        </div>
        <div class="row g-3 align-items-start">
            <div class="col-lg-3 col-md-6">
                <label class="form-label">Extra Category</label>
                <select name="category_id[]" class="form-select extra-category-select" required>
                    <option value="">Select category</option>
                    <?php foreach ($extraCategories as $cat): ?>
                        <option value="<?php echo (int)$cat['id']; ?>" data-category-name="<?php echo h(strtolower(trim((string)$cat['category']))); ?>">
                            <?php echo h($cat['category']); ?>
                        </option>
                    <?php endforeach; ?>
                </select>
            </div>

            <div class="col-lg-4 col-md-6">
                <label class="form-label">Description / Item</label>
                <div class="extra-picker-wrap">
                    <input type="hidden" name="service_id[]" class="extra-service-value" required>
                    <input type="search" class="form-control extra-service-search" placeholder="Select category first" disabled autocomplete="off">
                    <button type="button" class="extra-picker-clear" title="Clear item" aria-label="Clear item">&times;</button>
                    <div class="extra-picker-menu d-none"></div>
                </div>
                <div class="form-text extra-source-note">Restaurant = Food menu, Bar = all inventory items, Spar = Spar menu, Curio = inventory category 3.</div>
            </div>

            <div class="col-lg-2 col-md-4">
                <label class="form-label">Unit Price</label>
                <input type="number" step="0.01" name="unit_price[]" class="form-control extra-unit-price" value="0.00" required>
                <div class="form-text override-note extra-override-note d-none"></div>
            </div>

            <div class="col-lg-1 col-md-4">
                <label class="form-label">Qty</label>
                <input type="number" name="quantity[]" class="form-control extra-quantity" value="1" required>
            </div>

            <div class="col-lg-2 col-md-4">
                <label class="form-label">Amount</label>
                <input type="number" class="form-control extra-amount-preview" value="0.00" readonly>
            </div>
        </div>
    </div>
</template>

<script>
document.addEventListener('DOMContentLoaded', function () {
    var EXTRA_ITEM_OPTIONS = <?php echo $extraItemOptionsJson ?: '{}'; ?>;

    var moveModal = document.getElementById('moveFolioTxModal');
    if (moveModal) {
        moveModal.addEventListener('show.bs.modal', function (event) {
            var button = event.relatedTarget;
            if (!button) return;

            var txId = button.getAttribute('data-tx-id') || '';
            var currentFolioId = button.getAttribute('data-current-folio-id') || '';
            var description = button.getAttribute('data-description') || '';
            var txType = button.getAttribute('data-type') || '';
            var debit = button.getAttribute('data-debit') || '0.00';
            var credit = button.getAttribute('data-credit') || '0.00';

            var txInput = document.getElementById('move_folio_transaction_id');
            var descEl = document.getElementById('move_tx_description');
            var typeEl = document.getElementById('move_tx_type');
            var debitEl = document.getElementById('move_tx_debit');
            var creditEl = document.getElementById('move_tx_credit');
            var targetSelect = document.getElementById('move_target_folio_id');

            if (txInput) txInput.value = txId;
            if (descEl) descEl.textContent = description || 'Selected transaction';
            if (typeEl) typeEl.textContent = txType || 'Transaction';
            if (debitEl) debitEl.textContent = debit;
            if (creditEl) creditEl.textContent = credit;

            if (targetSelect) {
                targetSelect.value = '';
                Array.prototype.forEach.call(targetSelect.options, function(option) {
                    option.disabled = option.value !== '' && option.value === currentFolioId;
                });
            }
        });
    }

    var extraForm = document.getElementById('extraChargeForm');
    var container = document.getElementById('extraLinesContainer');
    var addBtn = document.getElementById('addExtraLineBtn');
    var template = document.getElementById('extraLineTemplate');
    var lineCountEl = document.getElementById('extraLineCount');
    var grandTotalEl = document.getElementById('extraGrandTotal');
    var postToSelect = document.getElementById('post_to');
    var otherAgentWrap = document.getElementById('otherAgentWrap');
    var otherAgentSelect = document.getElementById('other_agent_id');
    var otherAgentSearch = document.getElementById('other_agent_search');
    var otherAgentSelected = document.getElementById('otherAgentSelected');
    var clearOtherAgent = document.getElementById('clearOtherAgent');
    var PAYING_AGENTS = <?php echo $payingAgentsJson; ?>;
    var agentResultsPortal = null;
    var activeAgentIndex = -1;
    var visibleAgentResults = [];

    function ensureAgentResultsPortal() {
        if (agentResultsPortal) return agentResultsPortal;
        agentResultsPortal = document.createElement('div');
        agentResultsPortal.id = 'otherAgentResultsPortal';
        agentResultsPortal.className = 'extras-agent-results-portal';
        document.body.appendChild(agentResultsPortal);
        return agentResultsPortal;
    }

    function positionAgentResults() {
        if (!agentResultsPortal || !otherAgentSearch || !agentResultsPortal.classList.contains('is-open')) return;
        var rect = otherAgentSearch.getBoundingClientRect();
        var gap = 6;
        var maxHeight = 320;
        var below = window.innerHeight - rect.bottom - gap;
        var above = rect.top - gap;
        var openUp = below < 180 && above > below;
        var available = Math.max(120, Math.min(maxHeight, openUp ? above : below));
        agentResultsPortal.style.left = Math.max(8, rect.left) + 'px';
        agentResultsPortal.style.width = Math.max(260, rect.width) + 'px';
        agentResultsPortal.style.maxHeight = available + 'px';
        if (openUp) {
            agentResultsPortal.style.top = 'auto';
            agentResultsPortal.style.bottom = (window.innerHeight - rect.top + gap) + 'px';
        } else {
            agentResultsPortal.style.bottom = 'auto';
            agentResultsPortal.style.top = (rect.bottom + gap) + 'px';
        }
    }

    function closeAgentResults() {
        if (agentResultsPortal) agentResultsPortal.classList.remove('is-open');
        activeAgentIndex = -1;
    }

    function selectPayingAgent(agent) {
        if (!agent || !otherAgentSelect || !otherAgentSearch) return;
        otherAgentSelect.value = String(agent.id || '');
        otherAgentSearch.value = agent.name || '';
        if (otherAgentSelected) {
            otherAgentSelected.textContent = 'Selected: ' + (agent.name || ('Agent #' + agent.id));
            otherAgentSelected.classList.remove('d-none');
        }
        if (clearOtherAgent) clearOtherAgent.classList.remove('d-none');
        closeAgentResults();
    }

    function renderAgentResults() {
        if (!otherAgentSearch) return;
        var term = String(otherAgentSearch.value || '').trim().toLowerCase();
        visibleAgentResults = PAYING_AGENTS.filter(function(agent) {
            return !term || String(agent.name || '').toLowerCase().indexOf(term) !== -1 || String(agent.id || '').indexOf(term) !== -1;
        }).slice(0, 50);

        var portal = ensureAgentResultsPortal();
        portal.innerHTML = '';
        activeAgentIndex = -1;
        if (!visibleAgentResults.length) {
            var empty = document.createElement('div');
            empty.className = 'extras-agent-empty';
            empty.textContent = 'No matching agents found.';
            portal.appendChild(empty);
        } else {
            visibleAgentResults.forEach(function(agent, index) {
                var button = document.createElement('button');
                button.type = 'button';
                button.className = 'extras-agent-result';
                button.textContent = agent.name || ('Agent #' + agent.id);
                button.addEventListener('mousedown', function(e) {
                    e.preventDefault();
                    selectPayingAgent(agent);
                });
                portal.appendChild(button);
            });
        }
        portal.classList.add('is-open');
        positionAgentResults();
    }

    function toggleOtherAgent() {
        var isOther = postToSelect && postToSelect.value === 'other';
        if (otherAgentWrap) otherAgentWrap.classList.toggle('d-none', !isOther);
        if (!isOther) closeAgentResults();
    }
    if (postToSelect) {
        postToSelect.addEventListener('change', toggleOtherAgent);
        toggleOtherAgent();
    }

    if (otherAgentSearch) {
        otherAgentSearch.addEventListener('focus', renderAgentResults);
        otherAgentSearch.addEventListener('input', function() {
            if (otherAgentSelect) otherAgentSelect.value = '';
            if (otherAgentSelected) otherAgentSelected.classList.add('d-none');
            if (clearOtherAgent) clearOtherAgent.classList.toggle('d-none', !otherAgentSearch.value);
            renderAgentResults();
        });
        otherAgentSearch.addEventListener('keydown', function(e) {
            if (!agentResultsPortal || !agentResultsPortal.classList.contains('is-open')) {
                if (e.key === 'ArrowDown') renderAgentResults();
                return;
            }
            var buttons = agentResultsPortal.querySelectorAll('.extras-agent-result');
            if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
                e.preventDefault();
                if (!buttons.length) return;
                activeAgentIndex += e.key === 'ArrowDown' ? 1 : -1;
                if (activeAgentIndex < 0) activeAgentIndex = buttons.length - 1;
                if (activeAgentIndex >= buttons.length) activeAgentIndex = 0;
                Array.prototype.forEach.call(buttons, function(btn, idx) { btn.classList.toggle('active', idx === activeAgentIndex); });
                buttons[activeAgentIndex].scrollIntoView({block:'nearest'});
            } else if (e.key === 'Enter' && activeAgentIndex >= 0 && visibleAgentResults[activeAgentIndex]) {
                e.preventDefault();
                selectPayingAgent(visibleAgentResults[activeAgentIndex]);
            } else if (e.key === 'Escape') {
                closeAgentResults();
            }
        });
    }
    if (clearOtherAgent) {
        clearOtherAgent.addEventListener('click', function() {
            if (otherAgentSelect) otherAgentSelect.value = '';
            if (otherAgentSearch) { otherAgentSearch.value = ''; otherAgentSearch.focus(); }
            if (otherAgentSelected) otherAgentSelected.classList.add('d-none');
            clearOtherAgent.classList.add('d-none');
            renderAgentResults();
        });
    }
    document.addEventListener('mousedown', function(e) {
        if (e.target === otherAgentSearch || (agentResultsPortal && agentResultsPortal.contains(e.target))) return;
        closeAgentResults();
    });
    window.addEventListener('resize', positionAgentResults);
    window.addEventListener('scroll', positionAgentResults, true);

    if (!extraForm || !container || !addBtn || !template) return;

    function formatAmount(value) {
        var number = parseFloat(value || 0);
        if (isNaN(number)) number = 0;
        return number.toFixed(2);
    }

    function getLineRows() {
        return Array.prototype.slice.call(container.querySelectorAll('.extra-line-row'));
    }

    function optionMatchesSearch(option, term) {
        if (!term) return true;
        var text = [option.label || '', option.item_type || '', option.source || ''].join(' ').toLowerCase();
        return text.indexOf(term) !== -1;
    }

    function getCategoryOptions(row) {
        var categorySelect = row.querySelector('.extra-category-select');
        var categoryId = categorySelect ? String(categorySelect.value || '') : '';
        return categoryId && EXTRA_ITEM_OPTIONS[categoryId] ? EXTRA_ITEM_OPTIONS[categoryId] : [];
    }

    function closeAllPickers(exceptRow) {
        getLineRows().forEach(function(r) {
            if (exceptRow && r === exceptRow) return;
            var menu = r.querySelector('.extra-picker-menu');
            if (menu) menu.classList.add('d-none');
        });
    }

    function setSelectedService(row, opt, suppressRelated) {
        var serviceValue = row.querySelector('.extra-service-value');
        var serviceSearch = row.querySelector('.extra-service-search');
        var pickerWrap = row.querySelector('.extra-picker-wrap');
        var menu = row.querySelector('.extra-picker-menu');
        var unitPriceInput = row.querySelector('.extra-unit-price');

        if (!row.getAttribute('data-row-uid')) {
            row.setAttribute('data-row-uid', 'row_' + Date.now() + '_' + Math.floor(Math.random() * 100000));
        }

        if (serviceValue) {
            serviceValue.value = opt ? (opt.value || '') : '';
            serviceValue.setAttribute('data-price', opt ? (opt.price || 0) : 0);
            serviceValue.setAttribute('data-source', opt ? (opt.source || '') : '');
            serviceValue.setAttribute('data-type', opt ? (opt.item_type || '') : '');
        }
        if (serviceSearch) {
            serviceSearch.value = opt ? (opt.label || 'Item') : '';
            serviceSearch.placeholder = opt ? 'Search / change item...' : 'Search item / code / description...';
        }
        if (pickerWrap) pickerWrap.classList.toggle('has-value', !!opt);
        if (unitPriceInput) unitPriceInput.value = opt ? formatAmount(opt.price || 0) : '0.00';
        if (menu) menu.classList.add('d-none');
        calculateLine(row);

        if (!suppressRelated) {
            removeRelatedLinesForParent(row);
            if (opt && opt.related_items && opt.related_items.length) {
                addRelatedLinesForParent(row, opt.related_items);
            }
        }
    }

    function removeRelatedLinesForParent(parentRow) {
        var parentUid = parentRow.getAttribute('data-row-uid') || '';
        if (!parentUid) return;
        getLineRows().forEach(function(r) {
            if (r.getAttribute('data-auto-related-parent') === parentUid) {
                r.remove();
            }
        });
        refreshLineNumbers();
    }

    function chooseCategoryForOption(row, opt) {
        if (opt && opt.category_id) return String(opt.category_id);
        var categorySelect = row.querySelector('.extra-category-select');
        return categorySelect ? String(categorySelect.value || '') : '';
    }

    function addRelatedLinesForParent(parentRow, relatedItems) {
        var parentUid = parentRow.getAttribute('data-row-uid') || '';
        var qtyInput = parentRow.querySelector('.extra-quantity');
        var parentQty = qtyInput ? parseFloat(qtyInput.value || 1) : 1;
        if (isNaN(parentQty) || parentQty <= 0) parentQty = 1;

        relatedItems.slice(0, 2).forEach(function(related) {
            var relatedRow = addLine({silentFocus: true});
            relatedRow.classList.add('is-auto-related');
            relatedRow.setAttribute('data-auto-related-parent', parentUid);

            var badge = relatedRow.querySelector('.extra-related-badge');
            if (badge) badge.classList.remove('d-none');

            var categorySelect = relatedRow.querySelector('.extra-category-select');
            if (categorySelect) {
                categorySelect.value = chooseCategoryForOption(parentRow, related);
                categorySelect.dispatchEvent(new Event('change'));
            }

            var relQty = relatedRow.querySelector('.extra-quantity');
            if (relQty) relQty.value = String(parentQty);
            setSelectedService(relatedRow, related, true);
            calculateLine(relatedRow);
        });
        refreshLineNumbers();
    }

    function renderServiceOptions(row) {
        var serviceValue = row.querySelector('.extra-service-value');
        var serviceSearch = row.querySelector('.extra-service-search');
        var categorySelect = row.querySelector('.extra-category-select');
        var menu = row.querySelector('.extra-picker-menu');
        if (!serviceValue || !categorySelect || !menu) return;

        var categoryId = String(categorySelect.value || '');
        var term = serviceSearch ? String(serviceSearch.value || '').toLowerCase().trim() : '';
        var options = getCategoryOptions(row);

        menu.innerHTML = '';

        if (!categoryId) {
            serviceValue.value = '';
            serviceValue.disabled = true;
            if (serviceSearch) {
                serviceSearch.disabled = true;
                serviceSearch.value = '';
                serviceSearch.placeholder = 'Select category first';
            }
            menu.classList.add('d-none');
            return;
        }

        serviceValue.disabled = false;
        if (serviceSearch) {
            serviceSearch.disabled = false;
            if (!serviceSearch.placeholder || serviceSearch.placeholder === 'Select category first') serviceSearch.placeholder = 'Search item / code / description...';
        }

        var matched = 0;
        options.forEach(function(opt) {
            if (!optionMatchesSearch(opt, term)) return;
            var button = document.createElement('button');
            button.type = 'button';
            button.className = 'extra-picker-option';
            button.innerHTML = '<span><span class="extra-picker-label"></span><span class="extra-picker-meta"></span></span><span class="extra-picker-price"></span>';
            button.querySelector('.extra-picker-label').textContent = opt.label || 'Item';
            button.querySelector('.extra-picker-meta').textContent = (opt.item_type || 'Item') + (opt.source ? ' • ' + opt.source : '');
            button.querySelector('.extra-picker-price').textContent = formatAmount(opt.price || 0);
            button.addEventListener('click', function() { setSelectedService(row, opt); });
            menu.appendChild(button);
            matched++;
        });

        if (matched === 0) {
            var empty = document.createElement('div');
            empty.className = 'extra-picker-empty';
            empty.textContent = term ? 'No matching items found.' : 'No items found for this category.';
            menu.appendChild(empty);
        }
    }

    function selectedDefaultPrice(row) {
        var serviceValue = row.querySelector('.extra-service-value');
        var price = serviceValue ? parseFloat(serviceValue.getAttribute('data-price') || 0) : 0;
        return isNaN(price) ? 0 : price;
    }

    function isAdjustmentCategorySelected(row) {
        var categorySelect = row.querySelector('.extra-category-select');
        var selected = categorySelect ? categorySelect.options[categorySelect.selectedIndex] : null;
        var label = selected ? String(selected.getAttribute('data-category-name') || selected.textContent || '').toLowerCase().trim() : '';
        return label === 'adjustment' || label.indexOf('adjustment') !== -1;
    }

    function updateQuantityRules(row) {
        var qtyInput = row.querySelector('.extra-quantity');
        if (!qtyInput) return;

        if (isAdjustmentCategorySelected(row)) {
            qtyInput.removeAttribute('min');
            qtyInput.setAttribute('step', '0.01');
            qtyInput.placeholder = 'Credit: use negative';
        } else {
            qtyInput.setAttribute('min', '1');
            qtyInput.setAttribute('step', '1');
            qtyInput.placeholder = '';
            if (parseFloat(qtyInput.value || 0) < 1) {
                qtyInput.value = '1';
            }
        }
    }

    function updateOverrideHighlight(row) {
        var unitPriceInput = row.querySelector('.extra-unit-price');
        var note = row.querySelector('.extra-override-note');
        if (!unitPriceInput) return;

        var defaultPrice = selectedDefaultPrice(row);
        var currentPrice = parseFloat(unitPriceInput.value || 0);
        if (isNaN(currentPrice)) currentPrice = 0;

        var overridden = defaultPrice > 0 && Math.abs(currentPrice - defaultPrice) > 0.00001;
        unitPriceInput.classList.toggle('is-price-overridden', overridden);

        if (note) {
            note.classList.toggle('d-none', !overridden);
            note.textContent = overridden
                ? 'Default ' + formatAmount(defaultPrice) + ', entered ' + formatAmount(currentPrice) + '. Audit logged.'
                : '';
        }
    }

    function calculateLine(row) {
        var unitPriceInput = row.querySelector('.extra-unit-price');
        var qtyInput = row.querySelector('.extra-quantity');
        var amountInput = row.querySelector('.extra-amount-preview');

        var price = parseFloat(unitPriceInput ? unitPriceInput.value || 0 : 0);
        var qty = parseFloat(qtyInput ? qtyInput.value || 0 : 0);
        if (isNaN(price)) price = 0;
        if (isNaN(qty)) qty = 0;

        updateQuantityRules(row);
        updateOverrideHighlight(row);
        if (amountInput) amountInput.value = formatAmount(price * qty);
        updateSummary();
    }

    function filterServices(row) {
        var serviceSearch = row.querySelector('.extra-service-search');
        var amountInput = row.querySelector('.extra-amount-preview');
        if (serviceSearch) serviceSearch.value = '';
        setSelectedService(row, null);
        renderServiceOptions(row);
        if (amountInput) amountInput.value = '0.00';
        calculateLine(row);
    }

    function refreshLineNumbers() {
        var rows = getLineRows();
        rows.forEach(function(row, index) {
            var num = row.querySelector('.extra-line-number');
            var removeBtn = row.querySelector('.remove-extra-line-btn');
            if (num) num.textContent = String(index + 1);
            if (removeBtn) removeBtn.style.display = rows.length <= 1 ? 'none' : '';
        });
        updateSummary();
    }

    function updateSummary() {
        var rows = getLineRows();
        var total = 0;
        rows.forEach(function(row) {
            var amountInput = row.querySelector('.extra-amount-preview');
            var amount = parseFloat(amountInput ? amountInput.value || 0 : 0);
            if (!isNaN(amount)) total += amount;
        });
        if (lineCountEl) lineCountEl.textContent = String(rows.length);
        if (grandTotalEl) grandTotalEl.textContent = formatAmount(total);
    }

    function wireLine(row) {
        var categorySelect = row.querySelector('.extra-category-select');
        var serviceSearch = row.querySelector('.extra-service-search');
        var serviceValue = row.querySelector('.extra-service-value');
        var pickerMenu = row.querySelector('.extra-picker-menu');
        var clearBtn = row.querySelector('.extra-picker-clear');
        var unitPriceInput = row.querySelector('.extra-unit-price');
        var qtyInput = row.querySelector('.extra-quantity');
        var removeBtn = row.querySelector('.remove-extra-line-btn');

        if (categorySelect) {
            categorySelect.addEventListener('change', function () { filterServices(row); });
        }
        if (serviceSearch) {
            serviceSearch.addEventListener('focus', function () {
                if (categorySelect && categorySelect.value) {
                    closeAllPickers(row);
                    renderServiceOptions(row);
                    if (pickerMenu) pickerMenu.classList.remove('d-none');
                }
            });
            serviceSearch.addEventListener('input', function () {
                if (serviceValue) {
                    serviceValue.value = '';
                    serviceValue.setAttribute('data-price', '0');
                }
                if (unitPriceInput) unitPriceInput.value = '0.00';
                renderServiceOptions(row);
                if (pickerMenu) pickerMenu.classList.remove('d-none');
                calculateLine(row);
            });
        }
        if (clearBtn) {
            clearBtn.addEventListener('click', function () {
                setSelectedService(row, null);
                if (serviceSearch) serviceSearch.focus();
                renderServiceOptions(row);
                if (pickerMenu) pickerMenu.classList.remove('d-none');
            });
        }
        if (unitPriceInput) unitPriceInput.addEventListener('input', function () { calculateLine(row); });
        if (qtyInput) qtyInput.addEventListener('input', function () {
            calculateLine(row);
            var uid = row.getAttribute('data-row-uid') || '';
            if (uid && !row.getAttribute('data-auto-related-parent')) {
                getLineRows().forEach(function(r) {
                    if (r.getAttribute('data-auto-related-parent') === uid) {
                        var rq = r.querySelector('.extra-quantity');
                        if (rq) rq.value = qtyInput.value;
                        calculateLine(r);
                    }
                });
            }
        });
        if (removeBtn) {
            removeBtn.addEventListener('click', function () {
                if (getLineRows().length <= 1) return;
                row.remove();
                refreshLineNumbers();
            });
        }

        renderServiceOptions(row);
        calculateLine(row);
    }

    function addLine(options) {
        options = options || {};
        var node = template.content.firstElementChild.cloneNode(true);
        node.setAttribute('data-row-uid', 'row_' + Date.now() + '_' + Math.floor(Math.random() * 100000));
        container.appendChild(node);
        wireLine(node);
        refreshLineNumbers();
        return node;
    }

    addBtn.addEventListener('click', function () {
        var row = addLine();
        var categorySelect = row.querySelector('.extra-category-select');
        if (categorySelect) categorySelect.focus();
    });

    extraForm.addEventListener('submit', function (event) {
        if (postToSelect && postToSelect.value === 'other' && (!otherAgentSelect || !otherAgentSelect.value)) {
            event.preventDefault();
            alert('Please select the agent responsible for paying these extras.');
            if (otherAgentSearch) otherAgentSearch.focus();
            return;
        }
        if (getLineRows().length <= 0) {
            event.preventDefault();
            alert('Please add at least one extra line.');
            return;
        }

        getLineRows().forEach(function(row) { calculateLine(row); });
    });

    var modalEl = document.getElementById('extraChargeModal');
    if (modalEl) {
        modalEl.addEventListener('shown.bs.modal', function () {
            if (getLineRows().length === 0) {
                addLine();
            }
            updateSummary();
        });
    }


    document.addEventListener('click', function(event) {
        if (!event.target.closest('.extra-picker-wrap')) closeAllPickers(null);
    });

    if (getLineRows().length === 0) {
        addLine();
    }
});
</script>
<?php endif; ?>

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