<?php
/* front_office_pos.php
 * Full updated file:
 * - Spa items come from inventory_items category 1022
 * - Curio items come from inventory_items category 1010
 * - Search filters immediately while typing
 * - Database search keeps category filters grouped correctly
 * - Card/POS surcharge is 3% on purchase amount only
 * - Optional gratuity uses inventory item 2133 and has no surcharge
 * - Close with Payment and Post to Room both auto-print to PrintNode thermal printer
 */

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

/* Zimbabwe local time for PHP output, receipts and PrintNode payload text. */
date_default_timezone_set('Africa/Harare');
try {
    /* Zimbabwe is UTC+02:00 throughout the year. */
    $pdo->exec("SET time_zone = '+02:00'");
} catch (Throwable $e) {
    /* PHP date/time output still remains correctly set to Africa/Harare. */
}
// Shared cost-centre + supervisor-PIN system (same as the restaurant planner).
require_once __DIR__ . '/restaurant_cost_center_lib.php';

$pageTitle = 'Front Office POS';
const FO_POS_CARD_SURCHARGE_RATE = 0.03;
const FO_STANDARD_TAX_RATE = 15.5;
const FO_GRATUITY_INVENTORY_ITEM_ID = 2133;
const FO_PRINTNODE_MASTER_PRINTER_ID = 75588294;

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('fo_inclusive_tax_amount')) {
    function fo_inclusive_tax_amount(float $grossAmount, float $taxRate = FO_STANDARD_TAX_RATE): float {
        $grossAmount = round($grossAmount, 2);
        $taxRate = (float)$taxRate;
        if ($grossAmount <= 0 || $taxRate <= 0) return 0.00;
        return round($grossAmount * ($taxRate / (100 + $taxRate)), 2);
    }
}
if (!function_exists('fo_current_user_id')) {
    function fo_current_user_id(): ?int {
        foreach (['user_id','ss_user_id','id','uid','staffid'] as $k) {
            if (!empty($_SESSION[$k]) && (int)$_SESSION[$k] > 0) return (int)$_SESSION[$k];
        }
        return null;
    }
}
if (!function_exists('fo_current_user_name')) {
    function fo_current_user_name(): string {
        foreach (['currentUserName','current_user_name','user_name','username','full_name','name','staff_name','fullname','displayname','employee_name','consultant_name','agent_name'] as $k) {
            if (!empty($_SESSION[$k]) && trim((string)$_SESSION[$k]) !== '') return trim((string)$_SESSION[$k]);
        }

        // Session didn't have a display name under any of the usual keys - fall
        // back to looking the logged-in user up by ID, same reliable approach
        // used elsewhere in this system, rather than showing a generic label
        // for every single sale.
        $userId = fo_current_user_id();
        if ($userId !== null) {
            global $pdo;
            if ($pdo instanceof PDO) {
                $resolved = fo_resolve_user_name_from_db($pdo, $userId);
                if ($resolved !== '') return $resolved;
            }
        }

        return 'Front Office Cashier';
    }
}
if (!function_exists('fo_resolve_user_name_from_db')) {
    function fo_resolve_user_name_from_db(PDO $pdo, int $userId): string {
        static $cache = [];
        if ($userId <= 0) return '';
        if (array_key_exists($userId, $cache)) return $cache[$userId];

        $candidates = ['tblconsultant', 'consultants', 'users', 'system_users', 'staff'];
        $nameColSets = [
            ['first_name', 'last_name'],
            ['firstname', 'lastname'],
            ['name'],
            ['username'],
            ['full_name'],
        ];

        foreach ($candidates as $table) {
            try {
                $checkTable = $pdo->prepare("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t");
                $checkTable->execute([':t' => $table]);
                if ((int)$checkTable->fetchColumn() <= 0) continue;
            } catch (Throwable $e) {
                continue;
            }

            foreach ($nameColSets as $cols) {
                $allExist = true;
                foreach ($cols as $c) {
                    try {
                        $checkCol = $pdo->prepare("SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t AND COLUMN_NAME = :c");
                        $checkCol->execute([':t' => $table, ':c' => $c]);
                        if ((int)$checkCol->fetchColumn() <= 0) { $allExist = false; break; }
                    } catch (Throwable $e) {
                        $allExist = false;
                        break;
                    }
                }
                if (!$allExist) continue;

                $expr = count($cols) === 2
                    ? "TRIM(CONCAT(COALESCE(`{$cols[0]}`,''), ' ', COALESCE(`{$cols[1]}`,'')))"
                    : "`{$cols[0]}`";

                try {
                    $stmt = $pdo->prepare("SELECT {$expr} FROM `{$table}` WHERE id = :id LIMIT 1");
                    $stmt->execute([':id' => $userId]);
                    $name = trim((string)($stmt->fetchColumn() ?: ''));
                    if ($name !== '') {
                        $cache[$userId] = $name;
                        return $name;
                    }
                } catch (Throwable $ignore) {}

                break;
            }
        }

        $cache[$userId] = '';
        return '';
    }
}
if (!function_exists('fo_flash')) {
    function fo_flash(string $type, string $message): void { $_SESSION['fo_pos_flash'][] = ['type'=>$type,'message'=>$message]; }
}
if (!function_exists('fo_get_flash')) {
    function fo_get_flash(): array { $x = $_SESSION['fo_pos_flash'] ?? []; unset($_SESSION['fo_pos_flash']); return $x; }
}
if (!function_exists('fo_table_exists')) {
    function fo_table_exists(PDO $pdo, string $table): bool {
        static $cache = [];
        if (array_key_exists($table, $cache)) return $cache[$table];
        try {
            $s = $pdo->prepare("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = :t");
            $s->execute([':t'=>$table]);
            return $cache[$table] = ((int)$s->fetchColumn() > 0);
        } catch (Throwable $e) { return $cache[$table] = false; }
    }
}
if (!function_exists('fo_column_exists')) {
    function fo_column_exists(PDO $pdo, string $table, string $column): bool {
        static $cache = [];
        $key = $table.'.'.$column;
        if (array_key_exists($key, $cache)) return $cache[$key];
        try {
            $s = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = :t AND column_name = :c");
            $s->execute([':t'=>$table, ':c'=>$column]);
            return $cache[$key] = ((int)$s->fetchColumn() > 0);
        } catch (Throwable $e) { return $cache[$key] = false; }
    }
}
function fo_pos_sql_identifier(string $name): string {
    return '`' . str_replace('`', '``', $name) . '`';
}
function fo_first_existing_column(PDO $pdo, string $table, array $columns, ?string $fallback = null): ?string {
    foreach ($columns as $column) {
        if (fo_column_exists($pdo, $table, $column)) return $column;
    }
    return $fallback;
}

function fo_ensure_schema(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_type VARCHAR(40) NOT NULL DEFAULT 'front_office',
        linked_order_id INT UNSIGNED NULL,
        linked_folio_id INT UNSIGNED NULL,
        guest_name VARCHAR(180) NULL,
        room_no VARCHAR(80) NULL,
        group_name VARCHAR(180) NULL,
        covers INT UNSIGNED NOT NULL DEFAULT 1,
        status VARCHAR(40) NOT NULL DEFAULT 'open',
        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 VARCHAR(40) NOT NULL DEFAULT 'unpaid',
        opened_by INT UNSIGNED NULL,
        cashier_name VARCHAR(180) 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_status(status),
        INDEX idx_rest_order_type(order_type)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    /*
       Existing restaurant_orders may have been created by the restaurant/extras module
       with ENUM columns. Front Office POS needs order_type='front_office', so force
       these workflow columns to VARCHAR before inserting any FO sale.
    */
    try { $pdo->exec("ALTER TABLE restaurant_orders MODIFY order_type VARCHAR(40) NOT NULL DEFAULT 'front_office'"); } catch (Throwable $e) {}
    try { $pdo->exec("ALTER TABLE restaurant_orders MODIFY status VARCHAR(40) NOT NULL DEFAULT 'open'"); } catch (Throwable $e) {}
    try { $pdo->exec("ALTER TABLE restaurant_orders MODIFY payment_status VARCHAR(40) NOT NULL DEFAULT 'unpaid'"); } catch (Throwable $e) {}

    foreach ([
        'linked_folio_id INT UNSIGNED NULL AFTER linked_order_id',
        'room_no VARCHAR(80) NULL AFTER guest_name',
        'group_name VARCHAR(180) NULL AFTER room_no',
        'cashier_name VARCHAR(180) NULL AFTER opened_by'
    ] as $def) {
        $col = strtok($def, ' ');
        if (!fo_column_exists($pdo, 'restaurant_orders', $col)) {
            try { $pdo->exec("ALTER TABLE restaurant_orders ADD COLUMN $def"); } catch (Throwable $e) {}
        }
    }

    $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,
        inventory_item_id INT UNSIGNED NULL,
        item_name VARCHAR(180) NOT NULL,
        qty DECIMAL(14,4) NOT NULL DEFAULT 1,
        seat_no INT NULL,
        unit_price DECIMAL(14,2) NOT NULL DEFAULT 0,
        tax_rate DECIMAL(7,4) NOT NULL DEFAULT 0,
        tax_amount DECIMAL(14,2) NOT NULL DEFAULT 0,
        line_total DECIMAL(14,2) NOT NULL DEFAULT 0,
        printer_role VARCHAR(60) NULL,
        print_status VARCHAR(40) NOT NULL DEFAULT 'sent',
        notes VARCHAR(255) NULL,
        status VARCHAR(40) NOT NULL DEFAULT 'active',
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_rest_items_order(restaurant_order_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS restaurant_payments (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        restaurant_order_id BIGINT UNSIGNED NOT NULL,
        payment_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        payment_method VARCHAR(60) NOT NULL,
        amount DECIMAL(14,2) NOT NULL DEFAULT 0,
        bank_account_id INT UNSIGNED NULL,
        pos_machine_id INT UNSIGNED NULL,
        reference_no VARCHAR(120) NULL,
        created_by INT UNSIGNED NULL,
        status VARCHAR(40) NOT NULL DEFAULT 'posted',
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_rest_pay_order(restaurant_order_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS front_office_pos_day_closure (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        property_id INT UNSIGNED NULL,
        closure_date DATE NOT NULL,
        cashier_id INT UNSIGNED NULL,
        cashier_name VARCHAR(180) NULL,
        total_sales DECIMAL(14,2) NOT NULL DEFAULT 0,
        total_cash DECIMAL(14,2) NOT NULL DEFAULT 0,
        total_card DECIMAL(14,2) NOT NULL DEFAULT 0,
        total_folio DECIMAL(14,2) NOT NULL DEFAULT 0,
        notes TEXT NULL,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        UNIQUE KEY uq_fo_closure(property_id, closure_date, cashier_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
}

function fo_order_no(PDO $pdo): string {
    $prefix = 'FO-' . date('ymd') . '-';
    $s = $pdo->prepare("SELECT COUNT(*) FROM restaurant_orders WHERE order_no LIKE :p");
    $s->execute([':p'=>$prefix.'%']);
    return $prefix . str_pad((string)((int)$s->fetchColumn() + 1), 4, '0', STR_PAD_LEFT);
}
function fo_recalc(PDO $pdo, int $orderId): void {
    $s = $pdo->prepare("SELECT COALESCE(SUM(line_total),0) sub, COALESCE(SUM(tax_amount),0) tax FROM restaurant_order_items WHERE restaurant_order_id=:id AND status='active'");
    $s->execute([':id'=>$orderId]);
    $r = $s->fetch(PDO::FETCH_ASSOC) ?: [];
    $sub = round((float)($r['sub'] ?? 0), 2);
    $tax = round((float)($r['tax'] ?? 0), 2);
    $total = round($sub, 2);
    $p = $pdo->prepare("SELECT COALESCE(SUM(amount),0) FROM restaurant_payments WHERE restaurant_order_id=:id AND status='posted'");
    $p->execute([':id'=>$orderId]);
    $paid = round((float)$p->fetchColumn(), 2);
    $payStatus = $paid <= 0 ? 'unpaid' : ($paid + 0.0001 >= $total ? 'paid' : 'part_paid');
    $pdo->prepare("UPDATE restaurant_orders SET subtotal=:s,tax_total=:t,total_amount=:g,payment_status=:ps,updated_at=NOW() WHERE id=:id")
        ->execute([':s'=>$sub, ':t'=>$tax, ':g'=>$total, ':ps'=>$payStatus, ':id'=>$orderId]);
}
function fo_is_card_method(string $method): bool {
    return in_array(strtolower(trim($method)), ['pos','card','credit card','debit card','visa','mastercard','pos/card','card payment'], true);
}
function fo_add_gratuity_line(PDO $pdo, int $orderId, float $amount): void {
    $amount = round($amount, 2);
    if ($orderId <= 0 || $amount <= 0) return;

    $name = 'Gratuity';
    try {
        if (fo_table_exists($pdo, 'inventory_items')) {
            $nameCol = fo_first_existing_column($pdo, 'inventory_items', ['item_name','name','description','item_description','product_name'], 'id') ?: 'id';
            $s = $pdo->prepare("SELECT CAST(".fo_pos_sql_identifier($nameCol)." AS CHAR) AS item_name FROM inventory_items WHERE id=:id LIMIT 1");
            $s->execute([':id' => FO_GRATUITY_INVENTORY_ITEM_ID]);
            $rowName = trim((string)$s->fetchColumn());
            if ($rowName !== '') $name = $rowName;
        }
    } catch (Throwable $e) {
        $name = 'Gratuity';
    }

    $pdo->prepare("INSERT INTO restaurant_order_items
        (restaurant_order_id,menu_item_id,inventory_item_id,item_name,qty,unit_price,tax_rate,tax_amount,line_total,printer_role,print_status,notes,status)
        VALUES(:rid,NULL,:inv,:name,1,:unit,0,0,:line,'front_office','sent','Customer gratuity - no card/POS surcharge','active')")
        ->execute([
            ':rid'  => $orderId,
            ':inv'  => FO_GRATUITY_INVENTORY_ITEM_ID,
            ':name' => $name,
            ':unit' => $amount,
            ':line' => $amount,
        ]);
}

/*
   Discount is stored as a NEGATIVE order line, the same pattern already used
   for gratuity (positive, non-taxed) and the card surcharge (positive,
   taxed) lines. A negative line flows automatically through fo_recalc, the
   thermal receipt, the bill preview, folio posting and the day closure
   totals with no special handling anywhere - the discounted total is simply
   THE total everywhere downstream, including the 3% card/POS surcharge base.

   Only ONE discount per sale: applying again replaces the existing one, so
   a 10% discount re-applied never compounds. Discount lines are identified
   as: no menu/inventory item, negative line_total, name starting 'Discount'.
*/
function fo_void_discount_lines(PDO $pdo, int $orderId): void {
    $pdo->prepare("UPDATE restaurant_order_items
                   SET status='void', print_status='void'
                   WHERE restaurant_order_id=:rid AND status='active'
                     AND menu_item_id IS NULL AND inventory_item_id IS NULL
                     AND line_total < 0 AND item_name LIKE 'Discount%'")
        ->execute([':rid'=>$orderId]);
}

function fo_add_discount_line(PDO $pdo, int $orderId, float $amount, string $label, string $reason): void {
    $amount = round($amount, 2);
    if ($orderId <= 0 || $amount <= 0) return;

    /* Discount reduces the VAT-inclusive total, so carry a negative
       inclusive-tax portion too - keeps the VAT figure on receipts correct. */
    $tax = -fo_inclusive_tax_amount($amount, FO_STANDARD_TAX_RATE);

    $pdo->prepare("INSERT INTO restaurant_order_items
        (restaurant_order_id,menu_item_id,inventory_item_id,item_name,qty,unit_price,tax_rate,tax_amount,line_total,printer_role,print_status,notes,status)
        VALUES(:rid,NULL,NULL,:name,1,:unit,:tax_rate,:tax,:line,'front_office','sent',:notes,'active')")
        ->execute([
            ':rid'      => $orderId,
            ':name'     => $label,
            ':unit'     => -$amount,
            ':tax_rate' => FO_STANDARD_TAX_RATE,
            ':tax'      => $tax,
            ':line'     => -$amount,
            ':notes'    => trim($reason . ' | Applied by ' . fo_current_user_name()),
        ]);
}

function fo_card_surcharge_purchase_base(PDO $pdo, int $orderId): float {
    /*
       Chargeable purchase total only:
       - excludes gratuity inventory item 2133;
       - excludes any previously-created POS/Card surcharge line;
       - excludes voided lines.
       This makes the no-surcharge-on-gratuity rule enforceable in the backend,
       even if a browser submits an incorrect amount.
    */
    $stmt = $pdo->prepare("
        SELECT COALESCE(SUM(line_total),0)
        FROM restaurant_order_items
        WHERE restaurant_order_id = :order_id
          AND status = 'active'
          AND COALESCE(inventory_item_id,0) <> :gratuity_item_id
          AND LOWER(TRIM(COALESCE(item_name,''))) NOT LIKE 'pos/card surcharge%'
    ");
    $stmt->execute([
        ':order_id' => $orderId,
        ':gratuity_item_id' => FO_GRATUITY_INVENTORY_ITEM_ID,
    ]);
    return max(0, round((float)$stmt->fetchColumn(), 2));
}

function fo_add_payment(PDO $pdo, int $orderId, string $method, float $amount, string $reference = '', ?float $surchargeBase = null): void {
    $method = trim($method);
    if ($orderId <= 0 || $method === '' || $amount <= 0) return;

    /*
       $amount is the amount the customer is paying before card/POS surcharge.
       $surchargeBase is the purchase amount only. Gratuity is excluded from
       surcharge even when the payment method is Card/POS.
    */
    $base = round($amount, 2);
    $calculatedPurchaseBase = fo_card_surcharge_purchase_base($pdo, $orderId);
    $chargeableBase = $surchargeBase === null
        ? $calculatedPurchaseBase
        : min(max(0, round($surchargeBase, 2)), $calculatedPurchaseBase);
    $paymentAmount = $base;
    $surcharge = 0.00;

    if (fo_is_card_method($method)) {
        $surcharge = round($chargeableBase * FO_POS_CARD_SURCHARGE_RATE, 2);
        $paymentAmount = round($base + $surcharge, 2);
        if ($surcharge > 0) {
            $surchargeTax = fo_inclusive_tax_amount($surcharge, FO_STANDARD_TAX_RATE);
            $pdo->prepare("INSERT INTO restaurant_order_items(restaurant_order_id,menu_item_id,inventory_item_id,item_name,qty,unit_price,tax_rate,tax_amount,line_total,printer_role,print_status,notes,status)
                           VALUES(:rid,NULL,NULL,'POS/Card Surcharge 3%',1,:unit_amount,:tax_rate,:tax_amount,:line_amount,'front_office','sent',:notes,'active')")
                ->execute([
                    ':rid' => $orderId,
                    ':unit_amount' => $surcharge,
                    ':tax_rate' => FO_STANDARD_TAX_RATE,
                    ':tax_amount' => $surchargeTax,
                    ':line_amount' => $surcharge,
                    ':notes' => '3% surcharge on purchase amount '.money_fmt($chargeableBase).' only. Gratuity excluded.'
                ]);
        }
    }

    $ref = trim($reference);
    if ($surcharge > 0) {
        $ref = trim($ref . ($ref !== '' ? ' | ' : '') . 'Purchase '.money_fmt($chargeableBase).' + 3% '.money_fmt($surcharge).' | Gratuity excluded from surcharge');
    }

    $pdo->prepare("INSERT INTO restaurant_payments(restaurant_order_id,payment_method,amount,reference_no,created_by) VALUES(:rid,:m,:a,:r,:u)")
        ->execute([':rid'=>$orderId, ':m'=>$method, ':a'=>$paymentAmount, ':r'=>$ref !== '' ? $ref : null, ':u'=>fo_current_user_id()]);
    fo_recalc($pdo, $orderId);
}
/*
   Manager PIN validation for reversing closed sales. Mirrors the approach used
   on the calendar's admin-authorised moves: a user with an elevated role whose
   PIN/password matches may authorise a reversal. Checks common PIN columns and
   falls back to the account password (hashed or plain) so existing manager
   credentials work here too.
*/
function fo_manager_pin_valid(PDO $pdo, string $pin): bool {
    $pin = trim($pin);
    if ($pin === '') return false;
    if (!fo_table_exists($pdo, 'agents')) {
        // Fall back to whatever user table the POS uses.
        if (!fo_table_exists($pdo, 'users')) return false;
    }

    $userTable = fo_table_exists($pdo, 'agents') ? 'agents' : 'users';
    $pinCols = ['supervisor_pin','manager_pin','admin_pin','void_pin','pin','auth_pin','password','Password','user_password','password_hash'];
    $present = [];
    foreach ($pinCols as $c) {
        if (fo_column_exists($pdo, $userTable, $c)) $present[] = $c;
    }
    if (!$present) return false;

    // Optional role gate: only elevated roles may authorise, if a role column exists.
    $roleCol = null;
    foreach (['AccountTypeID','account_type_id','role','role_id','user_type'] as $rc) {
        if (fo_column_exists($pdo, $userTable, $rc)) { $roleCol = $rc; break; }
    }

    try {
        $rows = $pdo->query("SELECT " . implode(',', array_map(function($c){ return "`$c`"; }, $present))
            . ($roleCol ? ", `$roleCol` AS _role" : "")
            . " FROM `$userTable`")->fetchAll(PDO::FETCH_ASSOC);
    } catch (Throwable $e) {
        return false;
    }

    foreach ($rows as $r) {
        foreach ($present as $c) {
            $stored = (string)($r[$c] ?? '');
            if ($stored === '') continue;
            // Hashed password match.
            if (strlen($stored) >= 55 && password_verify($pin, $stored)) return true;
            // Plain PIN / password match.
            if (hash_equals($stored, $pin)) return true;
        }
    }
    return false;
}

function fo_close_if_paid(PDO $pdo, int $orderId): void {
    fo_recalc($pdo, $orderId);
    $s = $pdo->prepare("SELECT total_amount FROM restaurant_orders WHERE id=:id LIMIT 1");
    $s->execute([':id'=>$orderId]);
    $total = (float)$s->fetchColumn();
    $p = $pdo->prepare("SELECT COALESCE(SUM(amount),0) FROM restaurant_payments WHERE restaurant_order_id=:id AND status='posted'");
    $p->execute([':id'=>$orderId]);
    $paid = (float)$p->fetchColumn();

    if ($paid + 0.0001 >= $total && $total > 0) {
        $pdo->prepare("UPDATE restaurant_orders SET status='closed', closed_by=:u, closed_at=NOW(), updated_at=NOW() WHERE id=:id")
            ->execute([':u'=>fo_current_user_id(), ':id'=>$orderId]);
    }
}

function fo_recalc_folio(PDO $pdo, int $folioId): void {
    if (!fo_table_exists($pdo, 'folio_transactions') || !fo_table_exists($pdo, 'folios')) return;
    $s = $pdo->prepare("SELECT
        COALESCE(SUM(CASE WHEN COALESCE(is_voided,0)=0 AND LOWER(COALESCE(tx_type,'')) NOT IN ('payment','receipt','deposit','payment_received','folio_payment','guest_payment','payment_void','void_payment','payment_reversal','reversal','refund','payment_refund') THEN COALESCE(debit,0) ELSE 0 END),0) charges,
        COALESCE(SUM(CASE WHEN COALESCE(is_voided,0)=0 AND LOWER(COALESCE(tx_type,'')) IN ('payment','receipt','deposit','payment_received','folio_payment','guest_payment') THEN COALESCE(credit,0) ELSE 0 END),0) payments
        FROM folio_transactions WHERE folio_id=:id");
    $s->execute([':id'=>$folioId]);
    $r = $s->fetch(PDO::FETCH_ASSOC) ?: [];
    $c = (float)($r['charges'] ?? 0);
    $p = (float)($r['payments'] ?? 0);
    $pdo->prepare("UPDATE folios SET total_charges=:c,total_payments=:p,balance=:b,updated_at=NOW() WHERE id=:id")
        ->execute([':c'=>$c, ':p'=>$p, ':b'=>$c-$p, ':id'=>$folioId]);
}
function fo_sync_order_status(PDO $pdo, int $pmsOrderId): void {
    if (!fo_table_exists($pdo, 'folios') || !fo_table_exists($pdo, 'orders')) return;
    $s = $pdo->prepare("SELECT COALESCE(SUM(total_payments),0) p, COALESCE(SUM(balance),0) b FROM folios WHERE order_id=:id");
    $s->execute([':id'=>$pmsOrderId]);
    $r = $s->fetch(PDO::FETCH_ASSOC) ?: [];
    $paid = (float)($r['p'] ?? 0);
    $balance = (float)($r['b'] ?? 0);
    $status = $paid <= 0 ? 0 : ($balance <= 0.0001 ? 2 : 1);
    try { $pdo->prepare("UPDATE orders SET payment_status=:s WHERE id=:id")->execute([':s'=>$status, ':id'=>$pmsOrderId]); } catch (Throwable $e) {}
}
function fo_post_to_folio(PDO $pdo, int $frontOrderId, int $folioId, float $amount, string $description): void {
    if ($folioId <= 0 || $amount <= 0) return;

    /* Universal protection for any current or future caller of this function. */
    fo_require_non_master_folio($pdo, $folioId);

    $f = $pdo->prepare("SELECT f.id, f.order_id, COALESCE(f.window_no,1) AS window_no, o.property_id FROM folios f INNER JOIN orders o ON o.id=f.order_id WHERE f.id=:id AND COALESCE(f.status,1)=1 AND LOWER(TRIM(COALESCE(f.folio_type,''))) <> 'master' LIMIT 1");
    $f->execute([':id'=>$folioId]);
    $folio = $f->fetch(PDO::FETCH_ASSOC);
    if (!$folio) throw new RuntimeException('Selected folio is not active or does not exist.');

    $pdo->prepare("INSERT INTO folio_transactions(property_id,folio_id,order_id,tx_date,tx_type,description,debit,credit,source_table,source_id)
                   VALUES(:p,:f,:o,NOW(),'extra',:d,:amt,0,'front_office_pos',:rid)")
        ->execute([
            ':p'=>(int)$folio['property_id'], ':f'=>$folioId, ':o'=>(int)$folio['order_id'],
            ':d'=>$description, ':amt'=>round($amount,2), ':rid'=>$frontOrderId
        ]);

    fo_recalc_folio($pdo, $folioId);
    fo_sync_order_status($pdo, (int)$folio['order_id']);
}
function fo_front_order_extra_description(PDO $pdo, int $frontOrderId, string $orderNo, string $guestName = ''): string {
    $base = 'Front Office POS Extra - ' . $orderNo;
    $guestName = trim($guestName);
    if ($guestName !== '') $base .= ' - ' . $guestName;

    $lines = [];
    try {
        $stmt = $pdo->prepare("SELECT item_name, qty, line_total, tax_amount FROM restaurant_order_items WHERE restaurant_order_id=:id AND COALESCE(status,'active')='active' ORDER BY id ASC LIMIT 12");
        $stmt->execute([':id' => $frontOrderId]);
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) ?: [] as $row) {
            $name = trim((string)($row['item_name'] ?? ''));
            if ($name === '') continue;
            $qty = (float)($row['qty'] ?? 0);
            $amount = (float)($row['line_total'] ?? 0);
            $lines[] = $name . ' x ' . rtrim(rtrim(number_format($qty, 2, '.', ''), '0'), '.') . ' = ' . money_fmt($amount);
        }
    } catch (Throwable $e) {}
    if ($lines) $base .= ' | ' . implode('; ', $lines);
    return $base;
}

/* Inventory category setup */
function fo_spa_root_category_id(): int { return 1022; }
function fo_curio_root_category_id(): int { return 1010; }

function fo_inventory_category_table(PDO $pdo): ?string {
    foreach (['inventory_categories','inventory_item_categories','item_categories','stock_categories'] as $table) {
        if (fo_table_exists($pdo, $table)) return $table;
    }
    return null;
}
function fo_inventory_category_name_column(PDO $pdo, string $table): string {
    return fo_first_existing_column($pdo, $table, ['category_name','name','title','description'], 'id') ?: 'id';
}
function fo_inventory_category_ids(PDO $pdo, int $rootId): array {
    $catTable = fo_inventory_category_table($pdo);
    if (!$catTable || !fo_column_exists($pdo, $catTable, 'parent_id')) return [$rootId];

    $ids = [$rootId];
    $queue = [$rootId];
    $seen = [$rootId => true];

    while ($queue) {
        $parent = array_shift($queue);
        $s = $pdo->prepare("SELECT id FROM $catTable WHERE parent_id=:parent_id");
        $s->execute([':parent_id'=>$parent]);
        foreach ($s->fetchAll(PDO::FETCH_COLUMN) ?: [] as $id) {
            $id = (int)$id;
            if ($id > 0 && !isset($seen[$id])) {
                $seen[$id] = true;
                $ids[] = $id;
                $queue[] = $id;
            }
        }
    }

    return $ids;
}
function fo_fetch_inventory_children(PDO $pdo, int $parentId, int $rootCategoryId): array {
    $catTable = fo_inventory_category_table($pdo);
    if (!$catTable) return [];

    $parentId = $parentId > 0 ? $parentId : $rootCategoryId;
    $nameCol = fo_inventory_category_name_column($pdo, $catTable);

    if (!fo_column_exists($pdo, $catTable, 'parent_id')) return [];

    $active = fo_column_exists($pdo, $catTable, 'is_active') ? " AND COALESCE(is_active,1)=1" : "";
    $order = fo_column_exists($pdo, $catTable, 'sort_order') ? "sort_order ASC, $nameCol ASC" : "$nameCol ASC";

    $sql = "SELECT id, $nameCol AS category_name FROM $catTable WHERE parent_id=:parent_id $active ORDER BY $order";
    $s = $pdo->prepare($sql);
    $s->execute([':parent_id'=>$parentId]);

    return $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
}
function fo_fetch_inventory_category_name(PDO $pdo, int $categoryId, string $fallback = 'Items'): string {
    $catTable = fo_inventory_category_table($pdo);
    if (!$catTable || $categoryId <= 0) return $fallback;

    $nameCol = fo_inventory_category_name_column($pdo, $catTable);

    try {
        $s = $pdo->prepare("SELECT $nameCol FROM $catTable WHERE id=:id LIMIT 1");
        $s->execute([':id'=>$categoryId]);
        $name = trim((string)$s->fetchColumn());
        return $name !== '' ? $name : $fallback;
    } catch (Throwable $e) {
        return $fallback;
    }
}
function fo_fetch_inventory_items(PDO $pdo, int $rootCategoryId, int $categoryId = 0, string $search = '', string $sourceType = 'item'): array {
    if (!fo_table_exists($pdo, 'inventory_items')) return [];

    $categoryId = $categoryId > 0 ? $categoryId : $rootCategoryId;

    $categoryCol    = fo_first_existing_column($pdo, 'inventory_items', ['category_id','inventory_category_id','item_category_id','category']);
    $subcategoryCol = fo_first_existing_column($pdo, 'inventory_items', ['subcategory_id','sub_category_id','inventory_subcategory_id','item_subcategory_id']);
    $nameCol        = fo_first_existing_column($pdo, 'inventory_items', ['item_name','name','description','item_description','product_name'], 'id') ?: 'id';
    $codeCol        = fo_first_existing_column($pdo, 'inventory_items', ['item_code','code','sku','barcode','stock_code']);
    $priceCol       = fo_first_existing_column($pdo, 'inventory_items', ['selling_price','sale_price','retail_price','price','unit_price','standard_price','cost_price'], null);
    $taxCol         = fo_first_existing_column($pdo, 'inventory_items', ['tax_rate','vat_rate','sales_tax_rate'], null);
    $activeCol      = fo_first_existing_column($pdo, 'inventory_items', ['is_active','active','status'], null);
    $posCol         = fo_first_existing_column($pdo, 'inventory_items', ['is_pos_item','pos_item','show_on_pos'], null);

    $params = [];
    $where = "1=1";

    /*
       Curio fixed rule:
       Curio POS must show ALL POS-active inventory rows whose main category is 1010.
       The subcategory is only for grouping; it must not hide items like Eco Drop.

       Expected SQL for Curios:
         WHERE category_id = 1010
           AND COALESCE(is_active,1)=1
           AND COALESCE(is_pos_item,1)=1
    */
    if ($sourceType === 'curio' && $categoryCol) {
        $where .= " AND " . fo_pos_sql_identifier($categoryCol) . " = :curio_root_category_id";
        $params[':curio_root_category_id'] = (int)$rootCategoryId;
    } elseif ($categoryCol || $subcategoryCol) {
        $selectedIds = fo_inventory_category_ids($pdo, $categoryId);
        if (!$selectedIds) $selectedIds = [$categoryId];

        if ((int)$categoryId === (int)$rootCategoryId || $search !== '') {
            $whereParts = [];
            if ($categoryCol) {
                $whereParts[] = fo_pos_sql_identifier($categoryCol) . " = :root_category_id";
                $params[':root_category_id'] = (int)$rootCategoryId;
            }
            if ($subcategoryCol) {
                $whereParts[] = fo_pos_sql_identifier($subcategoryCol) . " = :root_subcategory_id";
                $params[':root_subcategory_id'] = (int)$rootCategoryId;
            }
            $where .= " AND (" . implode(' OR ', $whereParts) . ")";
        } else {
            $placeholders = [];
            foreach ($selectedIds as $i => $id) {
                $key = ':cat_' . $i;
                $placeholders[] = $key;
                $params[$key] = (int)$id;
            }

            $catChecks = [];
            if ($subcategoryCol) {
                $catChecks[] = fo_pos_sql_identifier($subcategoryCol) . " IN (" . implode(',', $placeholders) . ")";
            }
            if ($categoryCol) {
                $catChecks[] = fo_pos_sql_identifier($categoryCol) . " IN (" . implode(',', $placeholders) . ")";
            }
            $where .= " AND (" . implode(' OR ', $catChecks) . ")";
        }
    }

    if ($activeCol) {
        if ($activeCol === 'status') {
            $where .= " AND LOWER(COALESCE(status,'')) NOT IN ('inactive','disabled','deleted')";
        } else {
            $where .= " AND COALESCE(" . fo_pos_sql_identifier($activeCol) . ",1)=1";
        }
    }

    if ($posCol) {
        $where .= " AND COALESCE(" . fo_pos_sql_identifier($posCol) . ",1)=1";
    }

    if ($search !== '') {
        $where .= " AND (CAST(" . fo_pos_sql_identifier($nameCol) . " AS CHAR) LIKE :search_name";
        $params[':search_name'] = '%' . $search . '%';

        if ($codeCol) {
            $where .= " OR COALESCE(" . fo_pos_sql_identifier($codeCol) . ",'') LIKE :search_code";
            $params[':search_code'] = '%' . $search . '%';
        }

        $where .= ")";
    }

    $codeExpr  = $codeCol ? fo_pos_sql_identifier($codeCol) : "''";
    $priceExpr = $priceCol ? "COALESCE(" . fo_pos_sql_identifier($priceCol) . ",0)" : "0";
    $taxExpr   = $taxCol ? "COALESCE(" . fo_pos_sql_identifier($taxCol) . ",0)" : "0";
    $catExpr   = $categoryCol ? fo_pos_sql_identifier($categoryCol) : "NULL";
    $subExpr   = $subcategoryCol ? fo_pos_sql_identifier($subcategoryCol) : "NULL";

    $sql = "SELECT id,
                   CAST(" . fo_pos_sql_identifier($nameCol) . " AS CHAR) AS item_name,
                   $codeExpr AS item_code,
                   $priceExpr AS price,
                   $taxExpr AS tax_rate,
                   $catExpr AS category_id,
                   $subExpr AS subcategory_id,
                   :source_type AS source_type
            FROM inventory_items
            WHERE $where
            ORDER BY item_name ASC
            LIMIT 5000";

    $params[':source_type'] = $sourceType;

    $s = $pdo->prepare($sql);
    $s->execute($params);

    return $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
}

function fo_fetch_curio_children(PDO $pdo, int $parentId): array {
    return fo_fetch_inventory_children($pdo, $parentId, fo_curio_root_category_id());
}
function fo_fetch_curio_category_name(PDO $pdo, int $categoryId): string {
    return fo_fetch_inventory_category_name($pdo, $categoryId, 'Curios');
}
function fo_fetch_curio_items(PDO $pdo, int $categoryId = 0, string $search = ''): array {
    return fo_fetch_inventory_items($pdo, fo_curio_root_category_id(), $categoryId, $search, 'curio');
}

function fo_fetch_order(PDO $pdo, int $id): ?array {
    if ($id <= 0) return null;
    $s = $pdo->prepare("SELECT * FROM restaurant_orders WHERE id=:id LIMIT 1");
    $s->execute([':id'=>$id]);
    $row = $s->fetch(PDO::FETCH_ASSOC);
    return $row ?: null;
}
function fo_fetch_order_lines(PDO $pdo, int $id): array {
    if ($id <= 0) return [];
    $s = $pdo->prepare("SELECT * FROM restaurant_order_items WHERE restaurant_order_id=:id ORDER BY id ASC");
    $s->execute([':id'=>$id]);
    return $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
}
function fo_fetch_payments(PDO $pdo, int $id): array {
    if ($id <= 0) return [];
    $s = $pdo->prepare("SELECT * FROM restaurant_payments WHERE restaurant_order_id=:id ORDER BY id ASC");
    $s->execute([':id'=>$id]);
    return $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
}
function fo_fetch_inhouse_bookings(PDO $pdo, int $propertyId = 0): array {
    if (!fo_table_exists($pdo,'orders') || !fo_table_exists($pdo,'folios')) return [];

    $hasCheckedOutStatus = fo_table_exists($pdo,'order_rooming') && fo_column_exists($pdo,'order_rooming','checked_out_status');
    $roomingJoinFilter = $hasCheckedOutStatus ? " AND COALESCE(rmg.checked_out_status,0) <> 1" : "";
    $existsNotCheckedOut = $hasCheckedOutStatus
        ? " AND EXISTS (SELECT 1 FROM order_rooming rmg2 WHERE rmg2.order_id = o.id AND COALESCE(rmg2.checked_out_status,0) <> 1)"
        : "";

    $params = [];
    $where = "DATE(o.check_in) <= CURDATE() AND DATE(o.check_out) >= CURDATE() AND LOWER(COALESCE(o.status,'')) NOT IN ('cancelled','canceled','void','deleted','checked_out')" . $existsNotCheckedOut;

    if ($propertyId > 0) {
        $where .= " AND o.property_id=:p";
        $params[':p']=$propertyId;
    }

    /*
       A room's own order_rooming.checked_out_status is the real signal that a
       guest has departed - orders.status often never transitions to
       'checked_out' at the booking level (especially once at least one room
       in a multi-room booking is still occupied). Without this, a same-day
       room turnover shows the DEPARTED guest instead of the guest who just
       checked in, because both bookings still pass the order-level date
       range check for the same calendar day.
    */
    $sql = "SELECT o.id, o.order_no, o.check_in, o.check_out, p.name AS property_name,
                   TRIM(CONCAT(COALESCE(g.firstname,''),' ',COALESCE(g.lastname,''))) AS guest_name,
                   GROUP_CONCAT(DISTINCT COALESCE(rm.room_no, rmg.room_no) ORDER BY rmg.room_no SEPARATOR ', ') AS room_no
            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 order_rooming rmg ON rmg.order_id=o.id{$roomingJoinFilter}
            LEFT JOIN rooms rm ON rm.id=rmg.room_id
            WHERE $where
            GROUP BY o.id, o.order_no, o.check_in, o.check_out, p.name, guest_name
            ORDER BY o.check_out ASC, guest_name ASC
            LIMIT 250";

    $s = $pdo->prepare($sql);
    $s->execute($params);
    return $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
}
function fo_fetch_inhouse_booking_detail(PDO $pdo, int $orderId): ?array {
    if ($orderId <= 0 || !fo_table_exists($pdo,'orders')) return null;

    $hasCheckedOutStatus = fo_table_exists($pdo,'order_rooming') && fo_column_exists($pdo,'order_rooming','checked_out_status');
    $roomingJoinFilter = $hasCheckedOutStatus ? " AND COALESCE(rmg.checked_out_status,0) <> 1" : "";

    $sql = "SELECT o.id, o.order_no, o.check_in, o.check_out, o.property_id,
                   TRIM(CONCAT(COALESCE(g.firstname,''),' ',COALESCE(g.lastname,''))) AS guest_name,
                   GROUP_CONCAT(DISTINCT COALESCE(rm.room_no, rmg.room_no) ORDER BY rmg.room_no SEPARATOR ', ') AS room_no
            FROM orders o
            LEFT JOIN guests g ON g.id=o.guest_id
            LEFT JOIN order_rooming rmg ON rmg.order_id=o.id{$roomingJoinFilter}
            LEFT JOIN rooms rm ON rm.id=rmg.room_id
            WHERE o.id=:id
            GROUP BY o.id, o.order_no, o.check_in, o.check_out, o.property_id, guest_name
            LIMIT 1";

    $s = $pdo->prepare($sql);
    $s->execute([':id'=>$orderId]);
    $row = $s->fetch(PDO::FETCH_ASSOC);
    return $row ?: null;
}
function fo_fetch_folios_for_order(PDO $pdo, int $orderId): array {
    if ($orderId <= 0 || !fo_table_exists($pdo,'folios')) return [];

    /*
       Posting to Room must never expose or use the booking's Master Folio.
       Only active room/guest folios are returned to every POS dropdown.
    */
    $s = $pdo->prepare("SELECT id, folio_no, folio_name, folio_type, balance, COALESCE(window_no,1) AS window_no
                        FROM folios
                        WHERE order_id=:id
                          AND COALESCE(status,1)=1
                          AND LOWER(TRIM(COALESCE(folio_type,''))) <> 'master'
                        ORDER BY FIELD(LOWER(TRIM(COALESCE(folio_type,''))),'room','guest'), id ASC");
    $s->execute([':id'=>$orderId]);
    return $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
}

function fo_require_non_master_folio(PDO $pdo, int $folioId, ?int $expectedOrderId = null): array {
    if ($folioId <= 0) {
        throw new RuntimeException('Select a Room or Guest folio.');
    }

    $sql = "SELECT id, order_id, folio_no, folio_name, folio_type
            FROM folios
            WHERE id=:id
              AND COALESCE(status,1)=1
            LIMIT 1";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([':id'=>$folioId]);
    $folio = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$folio) {
        throw new RuntimeException('Selected folio is not active or does not exist.');
    }

    if (strtolower(trim((string)($folio['folio_type'] ?? ''))) === 'master') {
        throw new RuntimeException('Master Folios are excluded from Room posting. Select a Room or Guest folio.');
    }

    if ($expectedOrderId !== null && $expectedOrderId > 0 && (int)$folio['order_id'] !== $expectedOrderId) {
        throw new RuntimeException('Selected folio does not belong to the selected booking.');
    }

    return $folio;
}



if (!function_exists('fo_thermal_line')) {
    function fo_thermal_line(string $left, string $right = '', int $width = 42): string {
        $left = trim(preg_replace('/\s+/', ' ', $left));
        $right = trim(preg_replace('/\s+/', ' ', $right));
        if ($right === '') return mb_substr($left, 0, $width) . "\n";
        $space = $width - mb_strlen($left) - mb_strlen($right);
        if ($space < 1) {
            $left = mb_substr($left, 0, max(1, $width - mb_strlen($right) - 1));
            $space = $width - mb_strlen($left) - mb_strlen($right);
        }
        return $left . str_repeat(' ', max(1, $space)) . $right . "\n";
    }
}

if (!function_exists('fo_thermal_hr')) {
    function fo_thermal_hr(int $width = 42): string { return str_repeat('-', $width) . "\n"; }
}

if (!function_exists('fo_build_thermal_receipt')) {
    function fo_build_thermal_receipt(PDO $pdo, int $orderId): string {
        $order = fo_fetch_order($pdo, $orderId);
        if (!$order) throw new RuntimeException('Receipt not found.');

        $lines = fo_fetch_order_lines($pdo, $orderId);
        $payments = fo_fetch_payments($pdo, $orderId);
        $width = 42;

        $taxableGross = 0.00;
        $nonTaxable = 0.00;
        foreach ($lines as $line) {
            if (($line['status'] ?? '') !== 'active') continue;
            $gross = round((float)($line['line_total'] ?? 0), 2);
            if ((int)($line['inventory_item_id'] ?? 0) === FO_GRATUITY_INVENTORY_ITEM_ID) $nonTaxable += $gross;
            else $taxableGross += $gross;
        }
        $taxTotal = round((float)($order['tax_total'] ?? 0), 2);
        $netTaxable = round($taxableGross - $taxTotal, 2);
        $paidTotal = 0.00;
        foreach ($payments as $pay) {
            if (($pay['status'] ?? 'posted') === 'posted') $paidTotal += round((float)($pay['amount'] ?? 0), 2);
        }
        $balance = max(0, round((float)$order['total_amount'] - $paidTotal, 2));

        $out = "";
        $out .= str_pad('PIONEERS LODGE', $width, ' ', STR_PAD_BOTH) . "\n";
        $out .= str_pad('FRONT OFFICE POS RECEIPT', $width, ' ', STR_PAD_BOTH) . "\n";
        $out .= str_pad(date('Y-m-d H:i:s'), $width, ' ', STR_PAD_BOTH) . "\n";
        $out .= fo_thermal_hr($width);
        $out .= fo_thermal_line('Receipt', (string)($order['order_no'] ?? ('#'.$orderId)), $width);
        $out .= fo_thermal_line('Customer', (string)($order['guest_name'] ?: 'Walk-in Guest'), $width);
        if (!empty($order['room_no'])) $out .= fo_thermal_line('Room', (string)$order['room_no'], $width);
        $out .= fo_thermal_line('Cashier', (string)($order['cashier_name'] ?: fo_current_user_name()), $width);
        $out .= fo_thermal_hr($width);

        foreach ($lines as $line) {
            if (($line['status'] ?? '') !== 'active') continue;
            $name = trim((string)($line['item_name'] ?? 'Item'));
            $qty = rtrim(rtrim(number_format((float)($line['qty'] ?? 0), 2, '.', ''), '0'), '.');
            $unit = money_fmt((float)($line['unit_price'] ?? 0));
            $total = money_fmt((float)($line['line_total'] ?? 0));
            $out .= fo_thermal_line($name, $total, $width);
            $out .= fo_thermal_line('  ' . $qty . ' x ' . $unit, '', $width);
        }

        $out .= fo_thermal_hr($width);
        $out .= fo_thermal_line('Taxable gross', money_fmt($taxableGross), $width);
        $out .= fo_thermal_line('Net before VAT', money_fmt($netTaxable), $width);
        $out .= fo_thermal_line('VAT 15.5% incl.', money_fmt($taxTotal), $width);
        if ($nonTaxable > 0) $out .= fo_thermal_line('Non-tax gratuity', money_fmt($nonTaxable), $width);
        $out .= fo_thermal_line('TOTAL', money_fmt((float)$order['total_amount']), $width);
        $out .= fo_thermal_line('Paid', money_fmt($paidTotal), $width);
        $out .= fo_thermal_line('Balance', money_fmt($balance), $width);

        if ($payments) {
            $out .= fo_thermal_hr($width);
            foreach ($payments as $pay) {
                if (($pay['status'] ?? 'posted') !== 'posted') continue;
                $out .= fo_thermal_line((string)$pay['payment_method'], money_fmt((float)$pay['amount']), $width);
            }
        }
        $out .= fo_thermal_hr($width);
        $out .= "Note: Card/POS payments are subject\n";
        $out .= "to a 3% surcharge. Gratuity is not\n";
        $out .= "subject to the surcharge.\n";
        $out .= fo_thermal_hr($width);
        $out .= str_pad('Thank you.', $width, ' ', STR_PAD_BOTH) . "\n\n\n";
        return $out;
    }
}

if (!function_exists('fo_build_thermal_bill_preview')) {
    function fo_build_thermal_bill_preview(PDO $pdo, int $orderId): string {
        /*
           Pre-payment "Preview Bill": printed BEFORE the customer decides on
           gratuity or pays. Shows a blank Gratuity line and a signature line
           so the guest can write in the amount they wish to leave and sign,
           then hand it back to staff before the final invoice is printed.
           No Paid/Balance section here - nothing has been settled yet.
        */
        $order = fo_fetch_order($pdo, $orderId);
        if (!$order) throw new RuntimeException('Bill not found.');

        $lines = fo_fetch_order_lines($pdo, $orderId);
        $width = 42;

        $taxableGross = 0.00;
        $existingGratuity = 0.00;
        foreach ($lines as $line) {
            if (($line['status'] ?? '') !== 'active') continue;
            $gross = round((float)($line['line_total'] ?? 0), 2);
            if ((int)($line['inventory_item_id'] ?? 0) === FO_GRATUITY_INVENTORY_ITEM_ID) $existingGratuity += $gross;
            else $taxableGross += $gross;
        }
        $taxTotal = round((float)($order['tax_total'] ?? 0), 2);

        $out = "";
        $out .= str_pad('PIONEERS LODGE', $width, ' ', STR_PAD_BOTH) . "\n";
        $out .= str_pad('Reg# TIN 2001134870', $width, ' ', STR_PAD_BOTH) . "\n";
        $out .= str_pad('VAT# : 220068351', $width, ' ', STR_PAD_BOTH) . "\n";
        $out .= str_pad('info@pioneersvicalls.com', $width, ' ', STR_PAD_BOTH) . "\n";
        $out .= str_pad('www.pioneersvicalls.com', $width, ' ', STR_PAD_BOTH) . "\n";
        $out .= str_pad('******** CUSTOMER BILL ********', $width, ' ', STR_PAD_BOTH) . "\n";
        $out .= fo_thermal_hr($width);
        $out .= fo_thermal_line('Order', (string)($order['order_no'] ?? ('#'.$orderId)), $width);
        $out .= fo_thermal_line('Customer', (string)($order['guest_name'] ?: 'Walk-in Guest'), $width);
        if (!empty($order['room_no'])) $out .= fo_thermal_line('Room', (string)$order['room_no'], $width);
        $out .= fo_thermal_line('Printed', date('Y-m-d H:i:s'), $width);
        $out .= fo_thermal_line('Cashier', (string)($order['cashier_name'] ?: fo_current_user_name()), $width);
        $out .= fo_thermal_hr($width);

        foreach ($lines as $line) {
            if (($line['status'] ?? '') !== 'active') continue;
            if ((int)($line['inventory_item_id'] ?? 0) === FO_GRATUITY_INVENTORY_ITEM_ID) continue; // shown separately below, blank
            $name = trim((string)($line['item_name'] ?? 'Item'));
            $qty = rtrim(rtrim(number_format((float)($line['qty'] ?? 0), 2, '.', ''), '0'), '.');
            $unit = money_fmt((float)($line['unit_price'] ?? 0));
            $total = money_fmt((float)($line['line_total'] ?? 0));
            $out .= fo_thermal_line($name, $total, $width);
            $out .= fo_thermal_line('  ' . $qty . ' x ' . $unit, '', $width);
        }

        $out .= fo_thermal_hr($width);
        $out .= fo_thermal_line('Total Charges', money_fmt($taxableGross), $width);
        $out .= fo_thermal_line('Tax/VAT incl.', money_fmt($taxTotal), $width);
        if ($existingGratuity > 0) {
            $out .= fo_thermal_line('Gratuity (already added)', money_fmt($existingGratuity), $width);
            $out .= fo_thermal_line('GROSS TOTAL', money_fmt($taxableGross + $existingGratuity), $width);
        } else {
            $out .= fo_thermal_line('GROSS TOTAL', money_fmt($taxableGross), $width);
        }
        $out .= fo_thermal_hr($width);
        $out .= "Note: Card/POS payments are subject\n";
        $out .= "to a 3% surcharge. Gratuity is not\n";
        $out .= "subject to the surcharge.\n";
        $out .= fo_thermal_hr($width);

        if ($existingGratuity <= 0) {
            $out .= "\n";
            $out .= "Gratuity:  __________________________\n";
            $out .= "\n";
            $out .= "Total:     __________________________\n";
            $out .= "\n";
        }
        $out .= "Room/Folio:    __________________________\n";
        $out .= "\n";
        $out .= "Customer Name: __________________________\n";
        $out .= "\n";
        $out .= "Signature:     __________________________\n";
        $out .= "\n\n";
        $out .= str_pad('Thank you.', $width, ' ', STR_PAD_BOTH) . "\n\n\n";
        return $out;
    }
}

if (!function_exists('fo_printnode_send_raw')) {
    function fo_printnode_send_raw(int $printerId, string $rawBytes, string $title): array {
        $helper = __DIR__ . '/../payments/printnode_print.php';
        if (is_file($helper)) {
            require_once $helper;
        }
        if (function_exists('printnode_send_raw')) {
            return printnode_send_raw($printerId, $rawBytes, $title);
        }
        if (!defined('PRINTNODE_API_KEY') || trim((string)PRINTNODE_API_KEY) === '') {
            throw new RuntimeException('PrintNode API key is not configured. Add it in modules/payments/printnode_print.php or define PRINTNODE_API_KEY.');
        }
        if (!function_exists('curl_init')) {
            throw new RuntimeException('PHP cURL extension is required for PrintNode printing.');
        }
        $payload = json_encode([
            'printerId' => $printerId,
            'title' => $title,
            'contentType' => 'raw_base64',
            'content' => base64_encode($rawBytes),
            'source' => 'Front Office POS',
        ]);
        $ch = curl_init('https://api.printnode.com/printjobs');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $payload,
            CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
            CURLOPT_USERPWD => PRINTNODE_API_KEY . ':',
            CURLOPT_TIMEOUT => 20,
        ]);
        $response = curl_exec($ch);
        $err = curl_error($ch);
        $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        if ($response === false || $code < 200 || $code >= 300) {
            throw new RuntimeException('PrintNode failed: ' . ($err ?: ('HTTP ' . $code . ' ' . (string)$response)));
        }
        return ['ok' => true, 'job_id' => $response, 'error' => null, 'response' => $response];
    }
}

if (!function_exists('fo_printnode_receipt')) {
    function fo_printnode_receipt(PDO $pdo, int $orderId): array {
        $order = fo_fetch_order($pdo, $orderId);
        if (!$order) throw new RuntimeException('Receipt not found.');
        $text = fo_build_thermal_receipt($pdo, $orderId);
        // ESC/POS: initialize + text + feed + partial cut
        $raw = "\x1B\x40" . $text . "\x1D\x56\x42\x00";
        return fo_printnode_send_raw(FO_PRINTNODE_MASTER_PRINTER_ID, $raw, 'FO POS Receipt ' . ($order['order_no'] ?? ('#'.$orderId)));
    }
}

function fo_render_print_receipt(PDO $pdo, int $orderId): void {
    $order = fo_fetch_order($pdo, $orderId);
    if (!$order) {
        http_response_code(404);
        echo 'Receipt not found.';
        exit;
    }

    $lines = fo_fetch_order_lines($pdo, $orderId);
    $payments = fo_fetch_payments($pdo, $orderId);

    $taxableGross = 0.00;
    $nonTaxable = 0.00;
    foreach ($lines as $line) {
        if (($line['status'] ?? '') !== 'active') continue;
        $gross = round((float)($line['line_total'] ?? 0), 2);
        if ((int)($line['inventory_item_id'] ?? 0) === FO_GRATUITY_INVENTORY_ITEM_ID) {
            $nonTaxable += round((float)($line['line_total'] ?? 0), 2);
        } else {
            $taxableGross += round((float)($line['line_total'] ?? 0), 2);
        }
    }

    $taxTotal = round((float)($order['tax_total'] ?? 0), 2);
    $netTaxable = round($taxableGross - $taxTotal, 2);
    $paidTotal = 0.00;
    foreach ($payments as $pay) {
        if (($pay['status'] ?? 'posted') === 'posted') {
            $paidTotal += round((float)($pay['amount'] ?? 0), 2);
        }
    }

    ?><!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Receipt <?php echo h($order['order_no'] ?? ('#'.$orderId)); ?></title>
<style>
    body{font-family:Arial,Helvetica,sans-serif;margin:0;background:#f3f4f6;color:#111827}
    .receipt{width:320px;margin:20px auto;background:#fff;padding:14px;border:1px solid #e5e7eb}
    .center{text-align:center}.muted{color:#6b7280;font-size:11px}.line{border-top:1px dashed #9ca3af;margin:8px 0}
    table{width:100%;border-collapse:collapse;font-size:12px}td{padding:2px 0;vertical-align:top}.right{text-align:right}
    .total{font-weight:bold;font-size:14px}.btns{text-align:center;margin:12px}@media print{.btns{display:none}body{background:#fff}.receipt{margin:0;border:0;width:300px}}
</style>
</head>
<body>
<div class="btns">
    <button onclick="window.print()">Print Receipt</button>
    <button onclick="window.close()">Close</button>
</div>
<div class="receipt">
    <div class="center">
        <strong>Pioneers Lodge</strong><br>
        <span class="muted">Front Office POS Receipt</span><br>
        <span class="muted"><?php echo h(date('Y-m-d H:i:s')); ?></span>
    </div>
    <div class="line"></div>
    <table>
        <tr><td>Receipt</td><td class="right"><?php echo h($order['order_no'] ?? ('#'.$orderId)); ?></td></tr>
        <tr><td>Customer</td><td class="right"><?php echo h($order['guest_name'] ?: 'Walk-in Guest'); ?></td></tr>
        <?php if (!empty($order['room_no'])): ?><tr><td>Room</td><td class="right"><?php echo h($order['room_no']); ?></td></tr><?php endif; ?>
        <tr><td>Cashier</td><td class="right"><?php echo h($order['cashier_name'] ?: fo_current_user_name()); ?></td></tr>
    </table>
    <div class="line"></div>
    <table>
        <?php foreach ($lines as $line): ?>
            <?php if (($line['status'] ?? '') !== 'active') continue; ?>
            <tr>
                <td>
                    <?php echo h($line['item_name']); ?><br>
                    <span class="muted"><?php echo h(rtrim(rtrim(number_format((float)$line['qty'],2,'.',''),'0'),'.')); ?> x <?php echo money_fmt($line['unit_price']); ?></span>
                </td>
                <td class="right"><?php echo money_fmt((float)$line['line_total']); ?></td>
            </tr>
        <?php endforeach; ?>
    </table>
    <div class="line"></div>
    <table>
        <tr><td>Taxable gross</td><td class="right"><?php echo money_fmt($taxableGross); ?></td></tr>
        <tr><td>Net before VAT</td><td class="right"><?php echo money_fmt($netTaxable); ?></td></tr>
        <tr><td>VAT 15.5% included</td><td class="right"><?php echo money_fmt($taxTotal); ?></td></tr>
        <?php if ($nonTaxable > 0): ?><tr><td>Non-taxable gratuity</td><td class="right"><?php echo money_fmt($nonTaxable); ?></td></tr><?php endif; ?>
        <tr class="total"><td>Total</td><td class="right"><?php echo money_fmt($order['total_amount']); ?></td></tr>
        <tr><td>Paid</td><td class="right"><?php echo money_fmt($paidTotal); ?></td></tr>
        <tr><td>Balance</td><td class="right"><?php echo money_fmt(max(0, (float)$order['total_amount'] - $paidTotal)); ?></td></tr>
    </table>
    <?php if ($payments): ?>
        <div class="line"></div>
        <table>
            <?php foreach ($payments as $pay): ?>
                <tr><td><?php echo h($pay['payment_method']); ?></td><td class="right"><?php echo money_fmt($pay['amount']); ?></td></tr>
            <?php endforeach; ?>
        </table>
    <?php endif; ?>
    <div class="line"></div>
    <div class="center muted">Thank you.</div>
</div>
<script>window.addEventListener('load', function(){ setTimeout(function(){ window.print(); }, 250); });</script>
</body>
</html><?php
    exit;
}

if (!function_exists('fo_printnode_bill_preview')) {
    function fo_printnode_bill_preview(PDO $pdo, int $orderId): array {
        $order = fo_fetch_order($pdo, $orderId);
        if (!$order) throw new RuntimeException('Bill not found.');
        $text = fo_build_thermal_bill_preview($pdo, $orderId);
        // ESC/POS: initialize + text + feed + partial cut
        $raw = "\x1B\x40" . $text . "\x1D\x56\x42\x00";
        return fo_printnode_send_raw(FO_PRINTNODE_MASTER_PRINTER_ID, $raw, 'FO POS Bill Preview ' . ($order['order_no'] ?? ('#'.$orderId)));
    }
}

function fo_render_print_bill_preview(PDO $pdo, int $orderId): void {
    $order = fo_fetch_order($pdo, $orderId);
    if (!$order) {
        http_response_code(404);
        echo 'Bill not found.';
        exit;
    }

    $lines = fo_fetch_order_lines($pdo, $orderId);

    $taxableGross = 0.00;
    $existingGratuity = 0.00;
    foreach ($lines as $line) {
        if (($line['status'] ?? '') !== 'active') continue;
        $gross = round((float)($line['line_total'] ?? 0), 2);
        if ((int)($line['inventory_item_id'] ?? 0) === FO_GRATUITY_INVENTORY_ITEM_ID) {
            $existingGratuity += $gross;
        } else {
            $taxableGross += $gross;
        }
    }
    $taxTotal = round((float)($order['tax_total'] ?? 0), 2);

    ?><!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Bill Preview <?php echo h($order['order_no'] ?? ('#'.$orderId)); ?></title>
<style>
    body{font-family:Arial,Helvetica,sans-serif;margin:0;background:#f3f4f6;color:#111827}
    .receipt{width:320px;margin:20px auto;background:#fff;padding:14px;border:1px solid #e5e7eb}
    .center{text-align:center}.muted{color:#6b7280;font-size:11px}.line{border-top:1px dashed #9ca3af;margin:8px 0}
    table{width:100%;border-collapse:collapse;font-size:12px}td{padding:2px 0;vertical-align:top}.right{text-align:right}
    .total{font-weight:bold;font-size:14px}.btns{text-align:center;margin:12px}
    .note{background:#fff7ed;border:1px solid #fed7aa;border-radius:6px;padding:8px;font-size:11px;margin:8px 0}
    .sign-row{margin:10px 0;font-size:12px}.sign-row .fill{display:inline-block;border-bottom:1px solid #111827;min-width:170px}
    @media print{.btns{display:none}body{background:#fff}.receipt{margin:0;border:0;width:300px}}
</style>
</head>
<body>
<div class="btns">
    <button onclick="window.print()">Print Preview Bill</button>
    <button onclick="window.close()">Close</button>
</div>
<div class="receipt">
    <div class="center">
        <strong>Pioneers Lodge</strong><br>
        <span class="muted">Reg# TIN 2001134870 &bull; VAT# 220068351</span><br>
        <span class="muted">info@pioneersvicalls.com &bull; www.pioneersvicalls.com</span><br>
        <strong>CUSTOMER BILL</strong>
    </div>
    <div class="line"></div>
    <table>
        <tr><td>Order</td><td class="right"><?php echo h($order['order_no'] ?? ('#'.$orderId)); ?></td></tr>
        <tr><td>Customer</td><td class="right"><?php echo h($order['guest_name'] ?: 'Walk-in Guest'); ?></td></tr>
        <?php if (!empty($order['room_no'])): ?><tr><td>Room</td><td class="right"><?php echo h($order['room_no']); ?></td></tr><?php endif; ?>
        <tr><td>Printed</td><td class="right"><?php echo h(date('Y-m-d H:i:s')); ?></td></tr>
        <tr><td>Cashier</td><td class="right"><?php echo h($order['cashier_name'] ?: fo_current_user_name()); ?></td></tr>
    </table>
    <div class="line"></div>
    <table>
        <?php foreach ($lines as $line): ?>
            <?php if (($line['status'] ?? '') !== 'active') continue; ?>
            <?php if ((int)($line['inventory_item_id'] ?? 0) === FO_GRATUITY_INVENTORY_ITEM_ID) continue; ?>
            <tr>
                <td>
                    <?php echo h($line['item_name']); ?><br>
                    <span class="muted"><?php echo h(rtrim(rtrim(number_format((float)$line['qty'],2,'.',''),'0'),'.')); ?> x <?php echo money_fmt($line['unit_price']); ?></span>
                </td>
                <td class="right"><?php echo money_fmt((float)$line['line_total']); ?></td>
            </tr>
        <?php endforeach; ?>
    </table>
    <div class="line"></div>
    <table>
        <tr><td>Total Charges</td><td class="right"><?php echo money_fmt($taxableGross); ?></td></tr>
        <tr><td>Tax/VAT incl.</td><td class="right"><?php echo money_fmt($taxTotal); ?></td></tr>
        <?php if ($existingGratuity > 0): ?>
            <tr><td>Gratuity (already added)</td><td class="right"><?php echo money_fmt($existingGratuity); ?></td></tr>
            <tr class="total"><td>GROSS TOTAL</td><td class="right"><?php echo money_fmt($taxableGross + $existingGratuity); ?></td></tr>
        <?php else: ?>
            <tr class="total"><td>GROSS TOTAL</td><td class="right"><?php echo money_fmt($taxableGross); ?></td></tr>
        <?php endif; ?>
    </table>
    <div class="note">Note: Card/POS payments are subject to a 3% surcharge. Gratuity is not subject to the surcharge.</div>
    <div class="line"></div>
    <?php if ($existingGratuity <= 0): ?>
        <div class="sign-row">Gratuity: <span class="fill">&nbsp;</span></div>
        <div class="sign-row">Total: <span class="fill">&nbsp;</span></div>
    <?php endif; ?>
    <div class="sign-row">Room/Folio: <span class="fill">&nbsp;</span></div>
    <div class="sign-row">Customer Name: <span class="fill">&nbsp;</span></div>
    <div class="sign-row">Signature: <span class="fill">&nbsp;</span></div>
    <div class="line"></div>
    <div class="center muted">Thank you.</div>
</div>
<script>window.addEventListener('load', function(){ setTimeout(function(){ window.print(); }, 250); });</script>
</body>
</html><?php
    exit;
}

/* Main */
$errors = [];
try { fo_ensure_schema($pdo); } catch (Throwable $e) { $errors[] = $e->getMessage(); }

$propertyId         = (int)($_GET['property_id'] ?? $_POST['property_id'] ?? 0);
$section            = strtolower(trim((string)($_GET['section'] ?? $_POST['section'] ?? 'spa')));
if (!in_array($section, ['spa','curio'], true)) $section = 'spa';

$spaCategoryId      = (int)($_GET['spa_category_id'] ?? $_POST['spa_category_id'] ?? fo_spa_root_category_id());
$curioCategoryId    = (int)($_GET['curio_category_id'] ?? $_POST['curio_category_id'] ?? fo_curio_root_category_id());
if ($spaCategoryId <= 0) $spaCategoryId = fo_spa_root_category_id();
if ($curioCategoryId <= 0) $curioCategoryId = fo_curio_root_category_id();

$search             = trim((string)($_GET['q'] ?? ''));
$orderId            = (int)($_GET['front_order_id'] ?? $_POST['front_order_id'] ?? 0);
$selectedPmsOrderId = (int)($_GET['pms_order_id'] ?? $_POST['pms_order_id'] ?? 0);

if (isset($_GET['print_receipt']) && (int)$_GET['print_receipt'] === 1 && $orderId > 0) {
    fo_render_print_receipt($pdo, $orderId);
}

if (isset($_GET['printnode_receipt']) && (int)$_GET['printnode_receipt'] === 1 && $orderId > 0) {
    try {
        $result = fo_printnode_receipt($pdo, $orderId);
        fo_flash('success', 'Receipt sent to PrintNode master printer ' . FO_PRINTNODE_MASTER_PRINTER_ID . '.');
    } catch (Throwable $e) {
        fo_flash('danger', $e->getMessage());
    }
    header('Location: front_office_pos.php?front_order_id=' . $orderId . '&property_id=' . $propertyId);
    exit;
}

if (isset($_GET['print_bill_preview']) && (int)$_GET['print_bill_preview'] === 1 && $orderId > 0) {
    fo_render_print_bill_preview($pdo, $orderId);
}

if (isset($_GET['printnode_bill_preview']) && (int)$_GET['printnode_bill_preview'] === 1 && $orderId > 0) {
    try {
        $result = fo_printnode_bill_preview($pdo, $orderId);
        fo_flash('success', 'Preview bill (blank gratuity + signature) sent to PrintNode master printer ' . FO_PRINTNODE_MASTER_PRINTER_ID . '.');
    } catch (Throwable $e) {
        fo_flash('danger', $e->getMessage());
    }
    header('Location: front_office_pos.php?front_order_id=' . $orderId . '&property_id=' . $propertyId);
    exit;
}

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

        if ($action === 'new_sale') {
            $propertyId = (int)($_POST['property_id'] ?? 0);
            $saleType   = strtolower(trim((string)($_POST['sale_type'] ?? 'cash')));
            if (!in_array($saleType, ['cash','folio'], true)) $saleType = 'cash';

            $guestName  = trim((string)($_POST['guest_name'] ?? ''));
            $pmsOrderId = (int)($_POST['pms_order_id'] ?? 0);
            $folioId    = (int)($_POST['folio_id'] ?? 0);
            $roomNo     = trim((string)($_POST['room_no'] ?? ''));

            if ($saleType === 'folio' && ($pmsOrderId <= 0 || $folioId <= 0)) {
                throw new RuntimeException('Select an in-house booking and folio for a room/guest charge.');
            }

            if ($saleType === 'folio') {
                $bookingDetail = fo_fetch_inhouse_booking_detail($pdo, $pmsOrderId);
                if (!$bookingDetail) throw new RuntimeException('Selected in-house booking was not found.');

                /* Server-side protection: a crafted POST may not choose a Master Folio. */
                fo_require_non_master_folio($pdo, $folioId, $pmsOrderId);

                if ($guestName === '') $guestName = trim((string)($bookingDetail['guest_name'] ?? ''));
                if ($roomNo === '') $roomNo = trim((string)($bookingDetail['room_no'] ?? ''));
                if ($propertyId <= 0 && !empty($bookingDetail['property_id'])) $propertyId = (int)$bookingDetail['property_id'];
            }

            if ($guestName === '') $guestName = $saleType === 'folio' ? 'In-house Guest' : 'Walk-in Guest';

            $pdo->prepare("INSERT INTO restaurant_orders(property_id,area_id,table_id,order_no,order_type,linked_order_id,linked_folio_id,guest_name,room_no,covers,status,opened_by,cashier_name,notes)
                           VALUES(:p,NULL,NULL,:no,'front_office',:linked,:folio,:guest,:room,1,'open',:u,:cashier,:notes)")
                ->execute([
                    ':p'       => $propertyId > 0 ? $propertyId : null,
                    ':no'      => fo_order_no($pdo),
                    ':linked'  => $saleType === 'folio' ? $pmsOrderId : null,
                    ':folio'   => $saleType === 'folio' ? $folioId : null,
                    ':guest'   => $guestName,
                    ':room'    => $roomNo !== '' ? $roomNo : null,
                    ':u'       => fo_current_user_id(),
                    ':cashier' => fo_current_user_name(),
                    ':notes'   => $saleType === 'folio' ? 'Front office POS charge to PMS folio' : 'Front office POS direct sale'
                ]);

            $newId = (int)$pdo->lastInsertId();
            header('Location: front_office_pos.php?front_order_id='.$newId.'&property_id='.$propertyId.'&section='.$section);
            exit;
        }

        if ($action === 'add_inventory_item') {
            $orderId         = (int)($_POST['front_order_id'] ?? 0);
            $inventoryItemId = (int)($_POST['inventory_item_id'] ?? 0);
            $qty             = max(0.0001, (float)($_POST['qty'] ?? 1));

            if ($orderId <= 0 || $inventoryItemId <= 0) throw new RuntimeException('Open a sale and select an item.');

            $order = fo_fetch_order($pdo, $orderId);
            if (!$order || !in_array((string)$order['status'], ['open','sent'], true)) throw new RuntimeException('This sale is not open.');

            $nameCol   = fo_first_existing_column($pdo, 'inventory_items', ['item_name','name','description','item_description','product_name'], 'id') ?: 'id';
            $priceCol  = fo_first_existing_column($pdo, 'inventory_items', ['selling_price','sale_price','retail_price','price','unit_price','standard_price','cost_price'], null);
            $taxCol    = fo_first_existing_column($pdo, 'inventory_items', ['tax_rate','vat_rate','sales_tax_rate'], null);
            $priceExpr = $priceCol ? "COALESCE(".fo_pos_sql_identifier($priceCol).",0)" : "0";
            $taxExpr   = $taxCol ? "COALESCE(".fo_pos_sql_identifier($taxCol).",0)" : "0";

            $sql = "SELECT id, CAST(".fo_pos_sql_identifier($nameCol)." AS CHAR) AS item_name, $priceExpr AS price, $taxExpr AS tax_rate
                    FROM inventory_items WHERE id=:id LIMIT 1";
            $s = $pdo->prepare($sql);
            $s->execute([':id'=>$inventoryItemId]);
            $item = $s->fetch(PDO::FETCH_ASSOC);

            if (!$item) throw new RuntimeException('Inventory item not found.');

            $unit = round((float)$item['price'], 2);
            $line = round($qty * $unit, 2);
            $tax  = fo_inclusive_tax_amount($line, FO_STANDARD_TAX_RATE);
            $name = trim((string)$item['item_name']);
            if ($name === '') $name = 'Item #'.$inventoryItemId;

            $pdo->prepare("INSERT INTO restaurant_order_items(restaurant_order_id,menu_item_id,inventory_item_id,item_name,qty,unit_price,tax_rate,tax_amount,line_total,printer_role,print_status,notes,status)
                           VALUES(:rid,NULL,:inv,:name,:qty,:unit,:tax_rate,:tax,:line,'front_office','sent',:notes,'active')")
                ->execute([
                    ':rid'      => $orderId,
                    ':inv'      => $inventoryItemId,
                    ':name'     => $name,
                    ':qty'      => $qty,
                    ':unit'     => $unit,
                    ':tax_rate' => FO_STANDARD_TAX_RATE,
                    ':tax'      => $tax,
                    ':line'     => $line,
                    ':notes'    => trim((string)($_POST['notes'] ?? '')) ?: null
                ]);

            fo_recalc($pdo, $orderId);

            $backSection  = (string)($_POST['section'] ?? 'spa');
            $backSpaCat   = (int)($_POST['spa_category_id'] ?? fo_spa_root_category_id());
            $backCurioCat = (int)($_POST['curio_category_id'] ?? fo_curio_root_category_id());

            header('Location: front_office_pos.php?front_order_id='.$orderId.'&property_id='.$propertyId
                  .'&section='.urlencode($backSection)
                  .'&spa_category_id='.$backSpaCat
                  .'&curio_category_id='.$backCurioCat
                  .'&q='.urlencode($search));
            exit;
        }

        if ($action === 'update_qty') {
            $orderId = (int)($_POST['front_order_id'] ?? 0);
            $itemId  = (int)($_POST['item_id'] ?? 0);
            $qty     = max(0.0001, (float)($_POST['qty'] ?? 1));

            $s = $pdo->prepare("SELECT unit_price,tax_rate FROM restaurant_order_items WHERE id=:id AND restaurant_order_id=:rid LIMIT 1");
            $s->execute([':id'=>$itemId, ':rid'=>$orderId]);
            $item = $s->fetch(PDO::FETCH_ASSOC);
            if (!$item) throw new RuntimeException('Order line not found.');

            $line = round($qty * (float)$item['unit_price'], 2);
            $tax  = fo_inclusive_tax_amount($line, (float)$item['tax_rate']);

            $pdo->prepare("UPDATE restaurant_order_items SET qty=:qty,line_total=:line,tax_amount=:tax WHERE id=:id AND restaurant_order_id=:rid")
                ->execute([':qty'=>$qty, ':line'=>$line, ':tax'=>$tax, ':id'=>$itemId, ':rid'=>$orderId]);

            fo_recalc($pdo, $orderId);
            header('Location: front_office_pos.php?front_order_id='.$orderId.'&property_id='.$propertyId);
            exit;
        }

        if ($action === 'void_item') {
            $orderId = (int)($_POST['front_order_id'] ?? 0);
            $itemId  = (int)($_POST['item_id'] ?? 0);

            $pdo->prepare("UPDATE restaurant_order_items SET status='void',print_status='void' WHERE id=:id AND restaurant_order_id=:rid")
                ->execute([':id'=>$itemId, ':rid'=>$orderId]);

            fo_recalc($pdo, $orderId);
            header('Location: front_office_pos.php?front_order_id='.$orderId.'&property_id='.$propertyId);
            exit;
        }

        if ($action === 'discount_item') {
            $orderId = (int)($_POST['front_order_id'] ?? 0);
            $itemId  = (int)($_POST['item_id'] ?? 0);
            $mode    = strtolower(trim((string)($_POST['discount_mode'] ?? 'percent')));
            $value   = (float)($_POST['discount_value'] ?? 0);
            $reason  = trim((string)($_POST['discount_reason'] ?? ''));

            if (!in_array($mode, ['amount','percent'], true)) $mode = 'percent';
            if ($orderId <= 0 || $itemId <= 0) throw new RuntimeException('Select an item to discount.');
            if ($value <= 0) throw new RuntimeException('Enter a discount greater than zero.');

            // Load the target line to base the discount on.
            $s = $pdo->prepare("SELECT item_name, line_total FROM restaurant_order_items
                                WHERE id=:id AND restaurant_order_id=:rid AND status='active' LIMIT 1");
            $s->execute([':id'=>$itemId, ':rid'=>$orderId]);
            $line = $s->fetch(PDO::FETCH_ASSOC);
            if (!$line) throw new RuntimeException('Order line not found.');

            $lineTotal = round((float)$line['line_total'], 2);
            if ($lineTotal <= 0) throw new RuntimeException('This line has no positive amount to discount.');

            $discountAmount = ($mode === 'percent')
                ? round($lineTotal * ($value / 100), 2)
                : round($value, 2);
            if ($discountAmount <= 0) throw new RuntimeException('Discount works out to zero.');
            if ($discountAmount > $lineTotal) $discountAmount = $lineTotal; // never exceed the line

            $label = 'Discount - ' . (string)$line['item_name']
                   . ($mode === 'percent' ? ' (' . rtrim(rtrim(number_format($value,2),'0'),'.') . '%)' : '');

            $pdo->beginTransaction();
            // Remove any prior discount for THIS item so re-applying replaces, not compounds.
            $pdo->prepare("UPDATE restaurant_order_items
                           SET status='void', print_status='void'
                           WHERE restaurant_order_id=:rid AND status='active'
                             AND menu_item_id IS NULL AND inventory_item_id IS NULL
                             AND line_total < 0 AND item_name LIKE :like")
                ->execute([':rid'=>$orderId, ':like'=>'Discount - ' . str_replace(['%','_'],['\\%','\\_'],(string)$line['item_name']) . '%']);

            fo_add_discount_line($pdo, $orderId, $discountAmount, $label,
                trim(($reason !== '' ? $reason . ' | ' : '') . 'Item discount'));
            fo_recalc($pdo, $orderId);
            $pdo->commit();

            fo_flash('success', 'Discount of ' . number_format($discountAmount,2) . ' applied to ' . (string)$line['item_name'] . '.');
            header('Location: front_office_pos.php?front_order_id='.$orderId.'&property_id='.$propertyId);
            exit;
        }

        if ($action === 'apply_discount') {
            $orderId = (int)($_POST['front_order_id'] ?? 0);
            $mode    = strtolower(trim((string)($_POST['discount_mode'] ?? 'amount')));
            $value   = (float)($_POST['discount_value'] ?? 0);
            $reason  = trim((string)($_POST['discount_reason'] ?? ''));

            if (!in_array($mode, ['amount','percent'], true)) $mode = 'amount';
            if ($orderId <= 0) throw new RuntimeException('Open a sale first.');
            if ($value <= 0) throw new RuntimeException('Enter a discount amount or percentage greater than zero.');
            if ($reason === '') throw new RuntimeException('Enter a reason for the discount (shown on the bill audit).');

            $order = fo_fetch_order($pdo, $orderId);
            if (!$order || !in_array((string)$order['status'], ['open','sent'], true)) throw new RuntimeException('This sale is not open.');

            /* Replace any existing discount, THEN take the total as the base -
               so re-applying never compounds and percent is always calculated
               on the undiscounted bill. */
            fo_void_discount_lines($pdo, $orderId);
            fo_recalc($pdo, $orderId);
            $order = fo_fetch_order($pdo, $orderId);
            $base  = round((float)($order['total_amount'] ?? 0), 2);
            if ($base <= 0) throw new RuntimeException('There is nothing on this bill to discount.');

            if ($mode === 'percent') {
                if ($value > 100) throw new RuntimeException('Discount percentage cannot exceed 100%.');
                $discountAmount = round($base * $value / 100, 2);
                $label = 'Discount ' . rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.') . '%';
            } else {
                $discountAmount = round($value, 2);
                $label = 'Discount';
            }

            if ($discountAmount > $base) throw new RuntimeException('Discount ('.money_fmt($discountAmount).') cannot exceed the bill total ('.money_fmt($base).').');

            fo_add_discount_line($pdo, $orderId, $discountAmount, $label, $reason);
            fo_recalc($pdo, $orderId);

            fo_flash('success', $label.' of '.money_fmt($discountAmount).' applied. New total: '.money_fmt(max(0, $base - $discountAmount)).'.');
            header('Location: front_office_pos.php?front_order_id='.$orderId.'&property_id='.$propertyId);
            exit;
        }

        if ($action === 'remove_discount') {
            $orderId = (int)($_POST['front_order_id'] ?? 0);
            if ($orderId <= 0) throw new RuntimeException('Open a sale first.');

            $order = fo_fetch_order($pdo, $orderId);
            if (!$order || !in_array((string)$order['status'], ['open','sent'], true)) throw new RuntimeException('This sale is not open.');

            fo_void_discount_lines($pdo, $orderId);
            fo_recalc($pdo, $orderId);

            fo_flash('success', 'Discount removed.');
            header('Location: front_office_pos.php?front_order_id='.$orderId.'&property_id='.$propertyId);
            exit;
        }

        if ($action === 'cancel_empty_sale') {
            $orderId = (int)($_POST['front_order_id'] ?? 0);
            $cnt = $pdo->prepare("SELECT COUNT(*) FROM restaurant_order_items WHERE restaurant_order_id=:id AND status='active'");
            $cnt->execute([':id'=>$orderId]);

            if ((int)$cnt->fetchColumn() > 0) throw new RuntimeException('This sale has items. Void items first or close it.');

            $pdo->prepare("UPDATE restaurant_orders SET status='void', payment_status='void', closed_by=:u, closed_at=NOW(), updated_at=NOW() WHERE id=:id")
                ->execute([':u'=>fo_current_user_id(), ':id'=>$orderId]);

            fo_flash('success','Empty sale cancelled.');
            header('Location: front_office_pos.php?property_id='.$propertyId);
            exit;
        }

        if ($action === 'close_payment') {
            $orderId   = (int)($_POST['front_order_id'] ?? 0);
            $method    = trim((string)($_POST['payment_method'] ?? ''));
            $amount    = (float)($_POST['payment_amount'] ?? 0);
            $reference = trim((string)($_POST['reference_no'] ?? ''));
            $gratuity  = max(0, round((float)($_POST['gratuity_amount'] ?? 0), 2));

            if ($orderId <= 0 || $method === '' || $amount <= 0) throw new RuntimeException('Enter valid payment details.');

            fo_recalc($pdo, $orderId);
            $orderBeforePayment = fo_fetch_order($pdo, $orderId);
            if (!$orderBeforePayment) throw new RuntimeException('Sale not found.');

            $purchaseAmount = fo_card_surcharge_purchase_base($pdo, $orderId);
            $expectedAmount = round($purchaseAmount + $gratuity, 2);

            /* Keep the close-payment amount aligned to sale + gratuity before surcharge.
               The 3% card/POS surcharge is calculated only on $purchaseAmount. */
            if (abs($amount - $expectedAmount) > 0.01) {
                $amount = $expectedAmount;
            }

            $pdo->beginTransaction();
            if ($gratuity > 0) {
                fo_add_gratuity_line($pdo, $orderId, $gratuity);
            }
            fo_add_payment($pdo, $orderId, $method, $amount, $reference, $purchaseAmount);
            fo_close_if_paid($pdo, $orderId);
            $pdo->commit();

            /* Auto-print final receipt to PrintNode master thermal printer.
               Do not roll back or block the sale if the printer/API is offline. */
            try {
                fo_printnode_receipt($pdo, $orderId);
                fo_flash('success', 'Sale closed and receipt sent to PrintNode master printer ' . FO_PRINTNODE_MASTER_PRINTER_ID . '.');
            } catch (Throwable $printError) {
                fo_flash('danger', 'Sale closed, but PrintNode receipt failed: ' . $printError->getMessage());
            }

            header('Location: front_office_pos.php?front_order_id='.$orderId.'&property_id='.$propertyId);
            exit;
        }

        if ($action === 'post_to_folio') {
            $orderId = (int)($_POST['front_order_id'] ?? 0);
            $folioId = (int)($_POST['folio_id'] ?? 0);
            $order   = fo_fetch_order($pdo, $orderId);

            if (!$order) throw new RuntimeException('Sale not found.');

            fo_recalc($pdo, $orderId);
            $order  = fo_fetch_order($pdo, $orderId);
            $amount = round((float)$order['total_amount'], 2);

            if ($folioId <= 0) throw new RuntimeException('Select a Room or Guest folio.');
            if ($amount <= 0) throw new RuntimeException('There is no amount to post.');

            /*
               Final backend gate: even if someone manipulates the HTML or POST,
               Master Folios can never receive a Front Office POS room charge.
            */
            $expectedPmsOrderId = (int)($order['linked_order_id'] ?? 0);
            fo_require_non_master_folio(
                $pdo,
                $folioId,
                $expectedPmsOrderId > 0 ? $expectedPmsOrderId : null
            );

            $desc = fo_front_order_extra_description($pdo, $orderId, (string)$order['order_no'], (string)$order['guest_name']);

            $pdo->beginTransaction();
            fo_post_to_folio($pdo, $orderId, $folioId, $amount, $desc);
            $pdo->prepare("UPDATE restaurant_orders SET linked_folio_id=:folio, payment_status='room_charged', status='closed', closed_by=:u, closed_at=NOW(), updated_at=NOW() WHERE id=:id")
                ->execute([':folio'=>$folioId, ':u'=>fo_current_user_id(), ':id'=>$orderId]);
            $pdo->commit();

            /* Auto-print final receipt to PrintNode master thermal printer after posting to room.
               Do not roll back or block the room posting if the printer/API is offline. */
            try {
                fo_printnode_receipt($pdo, $orderId);
                fo_flash('success', 'Sale posted to PMS folio and receipt sent to PrintNode master printer ' . FO_PRINTNODE_MASTER_PRINTER_ID . '.');
            } catch (Throwable $printError) {
                fo_flash('danger', 'Sale posted to PMS folio, but PrintNode receipt failed: ' . $printError->getMessage());
            }

            header('Location: front_office_pos.php?front_order_id='.$orderId.'&property_id='.$propertyId);
            exit;
        }

        if ($action === 'post_to_cost_center') {
            $orderId      = (int)($_POST['front_order_id'] ?? 0);
            $costCenterId = (int)($_POST['cost_center_id'] ?? 0);
            $pin          = (string)($_POST['supervisor_pin'] ?? '');
            $reason       = trim((string)($_POST['cost_center_reason'] ?? ''));
            $note         = trim((string)($_POST['cost_center_note'] ?? ''));
            $discountType = strtolower(trim((string)($_POST['cc_discount_mode'] ?? 'none')));
            $discountVal  = (float)($_POST['cc_discount_value'] ?? 0);

            // Optional per-item selection; empty = post the full bill.
            $selectedItemIds = $_POST['cost_center_item_ids'] ?? [];
            if (!is_array($selectedItemIds)) $selectedItemIds = [];
            $selectedItemIds = array_values(array_unique(array_filter(array_map('intval', $selectedItemIds), function($v){ return $v > 0; })));

            if ($orderId <= 0) throw new RuntimeException('Open a sale first.');
            if ($costCenterId <= 0) throw new RuntimeException('Select a costing department.');

            // Resolve the cost centre name (the shared poster needs it).
            $ccName = '';
            foreach (r_load_cost_centers($pdo) as $cc) {
                if ((int)$cc['id'] === $costCenterId) { $ccName = (string)$cc['name']; break; }
            }
            if ($ccName === '') throw new RuntimeException('That costing department is not active.');

            // The shared function handles PIN, per-item selection, discount,
            // ledger posting, and closing the sale - identical to the planner.
            r_post_items_to_cost_center(
                $pdo, $orderId, $costCenterId, $ccName,
                $selectedItemIds, $reason, $pin, $note,
                $discountType, $discountVal
            );

            fo_flash('success', 'Posted to ' . $ccName . '.');
            header('Location: front_office_pos.php?front_order_id='.$orderId.'&property_id='.$propertyId);
            exit;
        }

        if ($action === 'reverse_closed_sale') {
            $orderId = (int)($_POST['front_order_id'] ?? 0);
            $mode    = strtolower(trim((string)($_POST['reverse_mode'] ?? 'reopen'))); // 'void' | 'reopen'
            $pin     = (string)($_POST['manager_pin'] ?? '');
            $reason  = trim((string)($_POST['reverse_reason'] ?? ''));

            if ($orderId <= 0) throw new RuntimeException('Invalid sale.');
            if (!in_array($mode, ['void','reopen'], true)) throw new RuntimeException('Invalid reversal type.');
            if (!r_supervisor_pin_valid($pdo, $pin)) throw new RuntimeException('Supervisor PIN not recognised or insufficient authority. Reversal denied.');

            $order = fo_fetch_order($pdo, $orderId);
            if (!$order) throw new RuntimeException('Sale not found.');
            if (strtolower((string)($order['status'] ?? '')) !== 'closed') {
                throw new RuntimeException('Only a CLOSED sale can be reversed.');
            }

            $userId = fo_current_user_id();
            $userName = fo_current_user_name();
            $note = 'Reversal (' . $mode . ') by ' . $userName . ($reason !== '' ? ' - ' . $reason : '');

            $pdo->beginTransaction();

            /* If this sale was posted to a PMS folio, reverse that posting so the
               guest's folio no longer carries the charge. We void the room-charge
               transaction(s) this sale created and recalc the folio. */
            $linkedFolioId = (int)($order['linked_folio_id'] ?? 0);
            if ($linkedFolioId > 0 && fo_table_exists($pdo, 'folio_transactions')) {
                $voidSet = "is_voided = 1";
                if (fo_column_exists($pdo, 'folio_transactions', 'voided_at')) $voidSet .= ", voided_at = NOW()";
                if (fo_column_exists($pdo, 'folio_transactions', 'voided_by')) $voidSet .= ", voided_by = " . (int)$userId;
                $pdo->prepare("
                    UPDATE folio_transactions
                    SET {$voidSet}
                    WHERE folio_id = :fid
                      AND (
                            (LOWER(COALESCE(source_table,'')) = 'restaurant_orders' AND source_id = :oid)
                         OR (LOWER(COALESCE(source_table,'')) = 'front_office' AND source_id = :oid2)
                      )
                      AND COALESCE(is_voided,0) = 0
                ")->execute([':fid'=>$linkedFolioId, ':oid'=>$orderId, ':oid2'=>$orderId]);
                fo_recalc_folio($pdo, $linkedFolioId);
            }

            if ($mode === 'void') {
                /* Full reversal: void payments and mark the sale voided/reversed.
                   Item lines are left intact for audit but the sale no longer
                   counts as a completed sale. */
                if (fo_table_exists($pdo, 'restaurant_payments')) {
                    $pdo->prepare("UPDATE restaurant_payments SET status='void', updated_at=NOW() WHERE restaurant_order_id=:id AND status='posted'")
                        ->execute([':id'=>$orderId]);
                }
                $sql = "UPDATE restaurant_orders SET status='reversed', payment_status='reversed', updated_at=NOW()";
                if (fo_column_exists($pdo, 'restaurant_orders', 'reversed_by')) $sql .= ", reversed_by=" . (int)$userId;
                if (fo_column_exists($pdo, 'restaurant_orders', 'reversed_at')) $sql .= ", reversed_at=NOW()";
                if (fo_column_exists($pdo, 'restaurant_orders', 'reverse_reason')) $sql .= ", reverse_reason=" . $pdo->quote($note);
                $sql .= " WHERE id=:id";
                $pdo->prepare($sql)->execute([':id'=>$orderId]);
                fo_flash('success', 'Closed sale fully reversed (voided). Folio posting and payments reversed.');
            } else {
                /* Reopen for editing: clear closed/paid state so staff can amend.
                   Payments are voided so the reopened sale reflects an open bill. */
                if (fo_table_exists($pdo, 'restaurant_payments')) {
                    $pdo->prepare("UPDATE restaurant_payments SET status='void', updated_at=NOW() WHERE restaurant_order_id=:id AND status='posted'")
                        ->execute([':id'=>$orderId]);
                }
                $sql = "UPDATE restaurant_orders SET status='open', payment_status='unpaid', closed_by=NULL, closed_at=NULL, linked_folio_id=NULL, updated_at=NOW()";
                if (fo_column_exists($pdo, 'restaurant_orders', 'reopened_by')) $sql .= ", reopened_by=" . (int)$userId;
                if (fo_column_exists($pdo, 'restaurant_orders', 'reopened_at')) $sql .= ", reopened_at=NOW()";
                $sql .= " WHERE id=:id";
                $pdo->prepare($sql)->execute([':id'=>$orderId]);
                fo_flash('success', 'Closed sale reopened for editing. Previous payment/posting reversed.');
            }

            fo_recalc($pdo, $orderId);
            $pdo->commit();

            header('Location: front_office_pos.php?front_order_id='.$orderId.'&property_id='.$propertyId);
            exit;
        }
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        $errors[] = $e->getMessage();
    }
}

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

$spaChildren       = fo_fetch_inventory_children($pdo, $spaCategoryId, fo_spa_root_category_id());
$spaCategoryName   = fo_fetch_inventory_category_name($pdo, $spaCategoryId, 'Spa');
$curioChildren     = fo_fetch_inventory_children($pdo, $curioCategoryId, fo_curio_root_category_id());
$curioCategoryName = fo_fetch_inventory_category_name($pdo, $curioCategoryId, 'Curios');

$items = $section === 'curio'
    ? fo_fetch_inventory_items($pdo, fo_curio_root_category_id(), $curioCategoryId, $search, 'curio')
    : fo_fetch_inventory_items($pdo, fo_spa_root_category_id(), $spaCategoryId, $search, 'spa');

$order          = fo_fetch_order($pdo, $orderId);
$orderLines     = $order ? fo_fetch_order_lines($pdo, (int)$order['id']) : [];
$payments       = $order ? fo_fetch_payments($pdo, (int)$order['id']) : [];
$inhouseBookings = fo_fetch_inhouse_bookings($pdo, $propertyId);
$flash          = fo_get_flash();

$activeOrders = [];
try {
    $params = [];
    $where  = "order_type='front_office' AND status IN ('open','sent')";
    if ($propertyId > 0) {
        $where .= " AND (property_id=:p OR property_id IS NULL)";
        $params[':p']=$propertyId;
    }
    $s = $pdo->prepare("SELECT * FROM restaurant_orders WHERE $where ORDER BY opened_at DESC LIMIT 30");
    $s->execute($params);
    $activeOrders = $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
} catch (Throwable $e) {}

/* Closed sales reprint list */
$closedFrom = trim((string)($_GET['closed_from'] ?? date('Y-m-d')));
$closedTo   = trim((string)($_GET['closed_to'] ?? date('Y-m-d')));
$closedQ    = trim((string)($_GET['closed_q'] ?? ''));
if (strtotime($closedFrom) === false) $closedFrom = date('Y-m-d');
if (strtotime($closedTo) === false) $closedTo = date('Y-m-d');

$closedOrders = [];
try {
    $params = [
        ':closed_from' => $closedFrom . ' 00:00:00',
        ':closed_to'   => $closedTo . ' 23:59:59',
    ];
    $where = "ro.order_type='front_office' AND ro.status='closed' AND COALESCE(ro.closed_at, ro.updated_at, ro.opened_at) BETWEEN :closed_from AND :closed_to";
    if ($propertyId > 0) {
        $where .= " AND (ro.property_id=:closed_property_id OR ro.property_id IS NULL)";
        $params[':closed_property_id'] = $propertyId;
    }
    if ($closedQ !== '') {
        $where .= " AND (ro.order_no LIKE :closed_q OR ro.guest_name LIKE :closed_q OR ro.room_no LIKE :closed_q OR ro.cashier_name LIKE :closed_q)";
        $params[':closed_q'] = '%' . $closedQ . '%';
    }

    $sql = "
        SELECT
            ro.*,
            COALESCE(SUM(CASE WHEN COALESCE(rp.status,'posted')='posted' THEN rp.amount ELSE 0 END),0) AS paid_amount,
            COUNT(DISTINCT roi.id) AS line_count
        FROM restaurant_orders ro
        LEFT JOIN restaurant_payments rp ON rp.restaurant_order_id = ro.id
        LEFT JOIN restaurant_order_items roi ON roi.restaurant_order_id = ro.id AND COALESCE(roi.status,'active')='active'
        WHERE $where
        GROUP BY ro.id
        ORDER BY COALESCE(ro.closed_at, ro.updated_at, ro.opened_at) DESC, ro.id DESC
        LIMIT 100
    ";
    $s = $pdo->prepare($sql);
    $s->execute($params);
    $closedOrders = $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
} catch (Throwable $e) {}

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

<div class="hero-band fo-pos-hero mb-3">
    <div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
        <div>
            <div class="page-eyebrow text-white-50"><i class="bi bi-shop"></i> Front Office</div>
            <div class="hero-title">Spa & Curios POS</div>
            <div class="hero-subtitle">Direct sales and PMS folio charges from inventory items.</div>
        </div>
        <div class="d-flex flex-wrap gap-2">
            <a href="front_office_pos.php<?php echo $propertyId > 0 ? '?property_id='.(int)$propertyId : ''; ?>" class="btn btn-sm btn-light"><i class="bi bi-plus-circle me-1"></i>New Sale</a>
            <a href="/rooms/modules/payments/frontdesk_dashboard.php" class="btn btn-sm btn-outline-light"><i class="bi bi-arrow-left me-1"></i>Front Desk</a>
        </div>
    </div>
</div>

<?php foreach ($flash as $f): ?>
    <div class="alert alert-<?php echo h($f['type'] === 'success' ? 'success' : ($f['type'] === 'danger' ? 'danger' : 'info')); ?> py-2"><?php echo h($f['message']); ?></div>
<?php endforeach; ?>

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

<div class="row g-3 fo-pos-workspace">
    <div class="col-xl-3 fo-pos-sidebar">
        <div class="card panel-card mb-3">
            <div class="card-header"><strong>Start / Select Sale</strong></div>
            <div class="card-body">
                <form method="post" class="row g-2">
                    <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                    <input type="hidden" name="action" value="new_sale">

                    <div class="col-12">
                        <label class="form-label">Property</label>
                        <select name="property_id" class="form-select form-select-sm">
                            <?php foreach ($properties as $p): ?>
                                <option value="<?php echo (int)$p['id']; ?>" <?php echo $propertyId === (int)$p['id'] ? 'selected' : ''; ?>>
                                    <?php echo h($p['name']); ?><?php echo !empty($p['code']) ? ' - '.h($p['code']) : ''; ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                    </div>

                    <div class="col-12">
                        <label class="form-label">Sale Type</label>
                        <select name="sale_type" id="sale_type" class="form-select form-select-sm">
                            <option value="cash">Walk-in / Direct Payment</option>
                            <option value="folio">Post to Room / In-house Folio</option>
                        </select>
                    </div>

                    <div class="col-12">
                        <label class="form-label">Guest / Customer Name</label>
                        <input type="text" name="guest_name" id="guest_name" class="form-control form-control-sm" placeholder="Walk-in Guest">
                    </div>

                    <div class="col-12 fo-folio-block d-none">
                        <label class="form-label">In-house Booking</label>
                        <select name="pms_order_id" id="pms_order_id" class="form-select form-select-sm">
                            <option value="0">Select booking</option>
                            <?php foreach ($inhouseBookings as $b): ?>
                                <?php $label = trim(($b['guest_name'] ?: 'Guest').' • '.($b['room_no'] ?: 'Unassigned').' • '.($b['order_no'] ?: '#'.$b['id'])); ?>
                                <option value="<?php echo (int)$b['id']; ?>" data-guest="<?php echo h($b['guest_name']); ?>" data-room="<?php echo h($b['room_no']); ?>"><?php echo h($label); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>

                    <div class="col-12 fo-folio-block d-none">
                        <label class="form-label">Folio</label>
                        <select name="folio_id" id="folio_id" class="form-select form-select-sm">
                            <option value="0">Select booking first</option>
                        </select>
                        <div id="fo_new_sale_master_warning" class="alert alert-warning py-2 px-2 small mt-2 d-none">
                            <i class="bi bi-exclamation-triangle-fill me-1"></i>This is the guest's <strong>MASTER account</strong>, which usually bills the whole group/company. Confirm this is intended.
                        </div>
                    </div>

                    <input type="hidden" name="room_no" id="room_no" value="">

                    <div class="col-12 d-grid">
                        <button class="btn btn-primary btn-sm"><i class="bi bi-receipt me-1"></i>Open Sale</button>
                    </div>
                </form>
            </div>
        </div>

        <div class="card panel-card mb-3">
            <div class="card-header"><strong>Open Front Office Sales</strong></div>
            <div class="list-group list-group-flush small">
                <?php if (!$activeOrders): ?>
                    <div class="list-group-item text-muted">No open sales.</div>
                <?php endif; ?>

                <?php foreach ($activeOrders as $ao): ?>
                    <a class="list-group-item list-group-item-action <?php echo $order && (int)$order['id'] === (int)$ao['id'] ? 'active' : ''; ?>" href="front_office_pos.php?front_order_id=<?php echo (int)$ao['id']; ?>&property_id=<?php echo (int)$propertyId; ?>">
                        <div class="d-flex justify-content-between">
                            <strong><?php echo h($ao['order_no']); ?></strong>
                            <span><?php echo money_fmt($ao['total_amount']); ?></span>
                        </div>
                        <div class="small <?php echo $order && (int)$order['id'] === (int)$ao['id'] ? 'text-white-50' : 'text-muted'; ?>"><?php echo h($ao['guest_name'] ?: 'Walk-in Guest'); ?></div>
                    </a>
                <?php endforeach; ?>
            </div>
        </div>

        <div class="card panel-card fo-closed-sales-card">
            <div class="card-header d-flex justify-content-between align-items-center">
                <strong>Closed Sales / Reprint</strong>
                <span class="badge text-bg-secondary"><?php echo count($closedOrders); ?></span>
            </div>
            <div class="card-body py-2">
                <form method="get" class="row g-1">
                    <input type="hidden" name="property_id" value="<?php echo (int)$propertyId; ?>">
                    <input type="hidden" name="front_order_id" value="<?php echo (int)$orderId; ?>">
                    <input type="hidden" name="section" value="<?php echo h($section); ?>">
                    <input type="hidden" name="spa_category_id" value="<?php echo (int)$spaCategoryId; ?>">
                    <input type="hidden" name="curio_category_id" value="<?php echo (int)$curioCategoryId; ?>">
                    <div class="col-6">
                        <input type="date" name="closed_from" class="form-control form-control-sm" value="<?php echo h($closedFrom); ?>">
                    </div>
                    <div class="col-6">
                        <input type="date" name="closed_to" class="form-control form-control-sm" value="<?php echo h($closedTo); ?>">
                    </div>
                    <div class="col-9">
                        <input type="text" name="closed_q" class="form-control form-control-sm" value="<?php echo h($closedQ); ?>" placeholder="Receipt, guest, room, cashier">
                    </div>
                    <div class="col-3 d-grid">
                        <button class="btn btn-sm btn-outline-primary"><i class="bi bi-search"></i></button>
                    </div>
                </form>
            </div>
            <div class="list-group list-group-flush small fo-closed-sales-list">
                <?php if (!$closedOrders): ?>
                    <div class="list-group-item text-muted">No closed sales found for this filter.</div>
                <?php endif; ?>

                <?php foreach ($closedOrders as $co): ?>
                    <?php
                        $closedAt = trim((string)($co['closed_at'] ?? $co['updated_at'] ?? $co['opened_at'] ?? ''));
                        $closedLabel = $closedAt !== '' ? date('d M H:i', strtotime($closedAt)) : '';
                    ?>
                    <div class="list-group-item">
                        <div class="d-flex justify-content-between gap-2">
                            <div class="min-w-0">
                                <strong><?php echo h($co['order_no']); ?></strong>
                                <div class="text-muted"><?php echo h($co['guest_name'] ?: 'Walk-in Guest'); ?><?php echo !empty($co['room_no']) ? ' • Room '.h($co['room_no']) : ''; ?></div>
                                <div class="text-muted"><?php echo h($closedLabel); ?><?php echo !empty($co['cashier_name']) ? ' • '.h($co['cashier_name']) : ''; ?></div>
                            </div>
                            <div class="text-end flex-shrink-0">
                                <div class="fw-bold"><?php echo money_fmt($co['total_amount']); ?></div>
                                <div class="btn-group btn-group-sm mt-1" role="group">
                                    <a class="btn btn-outline-dark" title="Open / Reverse this sale"
                                       href="front_office_pos.php?front_order_id=<?php echo (int)$co['id']; ?>&property_id=<?php echo (int)$propertyId; ?>">
                                        <i class="bi bi-box-arrow-in-up-right"></i>
                                    </a>
                                    <a class="btn btn-outline-secondary" target="_blank" title="Browser print"
                                       href="front_office_pos.php?front_order_id=<?php echo (int)$co['id']; ?>&property_id=<?php echo (int)$propertyId; ?>&print_receipt=1">
                                        <i class="bi bi-printer"></i>
                                    </a>
                                    <a class="btn btn-primary" title="PrintNode reprint"
                                       href="front_office_pos.php?front_order_id=<?php echo (int)$co['id']; ?>&property_id=<?php echo (int)$propertyId; ?>&printnode_receipt=1"
                                       onclick="return confirm('Reprint receipt <?php echo h($co['order_no']); ?> to PrintNode master printer <?php echo FO_PRINTNODE_MASTER_PRINTER_ID; ?>?');">
                                        <i class="bi bi-printer-fill"></i>
                                    </a>
                                </div>
                            </div>
                        </div>
                    </div>
                <?php endforeach; ?>
            </div>
        </div>
    </div>

    <div class="col-xl-5 fo-pos-catalogue">
        <div class="card panel-card mb-3 fo-browser-card">
            <div class="card-body py-2">
                <div class="d-flex flex-wrap gap-2 mb-2">
                    <a class="btn btn-sm <?php echo $section === 'spa' ? 'btn-primary' : 'btn-outline-primary'; ?>"
                       href="front_office_pos.php?front_order_id=<?php echo (int)$orderId; ?>&property_id=<?php echo (int)$propertyId; ?>&section=spa&spa_category_id=<?php echo fo_spa_root_category_id(); ?>&curio_category_id=<?php echo (int)$curioCategoryId; ?>">
                        <i class="bi bi-droplet me-1"></i>Spa
                    </a>

                    <a class="btn btn-sm <?php echo $section === 'curio' ? 'btn-primary' : 'btn-outline-primary'; ?>"
                       href="front_office_pos.php?front_order_id=<?php echo (int)$orderId; ?>&property_id=<?php echo (int)$propertyId; ?>&section=curio&spa_category_id=<?php echo (int)$spaCategoryId; ?>&curio_category_id=<?php echo fo_curio_root_category_id(); ?>">
                        <i class="bi bi-bag-heart me-1"></i>Curios
                    </a>
                </div>

                <form method="get" id="fo_search_form" class="row g-2 align-items-end">
                    <input type="hidden" name="front_order_id" value="<?php echo (int)$orderId; ?>">
                    <input type="hidden" name="property_id" value="<?php echo (int)$propertyId; ?>">
                    <input type="hidden" name="section" value="<?php echo h($section); ?>">
                    <input type="hidden" name="spa_category_id" value="<?php echo (int)$spaCategoryId; ?>">
                    <input type="hidden" name="curio_category_id" value="<?php echo (int)$curioCategoryId; ?>">

                    <div class="col-md-9">
                        <label class="form-label">Search <?php echo $section === 'curio' ? 'Curios' : 'Spa'; ?></label>
                        <input type="text" id="fo_live_search" name="q" class="form-control form-control-sm" value="<?php echo h($search); ?>" placeholder="Type item name or code...">
                        <div class="form-text small">Filters immediately while typing. Press Enter or Search for database search.</div>
                    </div>

                    <div class="col-md-3 d-grid">
                        <button class="btn btn-outline-secondary btn-sm">Search</button>
                    </div>
                </form>
            </div>
        </div>

        <?php
        $activeChildren   = $section === 'spa' ? $spaChildren : $curioChildren;
        $activeCategoryId = $section === 'spa' ? $spaCategoryId : $curioCategoryId;
        $activeRootId     = $section === 'spa' ? fo_spa_root_category_id() : fo_curio_root_category_id();
        $activeCatName    = $section === 'spa' ? $spaCategoryName : $curioCategoryName;
        $activeCatParam   = $section === 'spa' ? 'spa_category_id' : 'curio_category_id';
        ?>

        <div class="fo-breadcrumbs mb-2">
            <a href="front_office_pos.php?front_order_id=<?php echo (int)$orderId; ?>&property_id=<?php echo (int)$propertyId; ?>&section=<?php echo h($section); ?>&<?php echo $activeCatParam; ?>=<?php echo (int)$activeRootId; ?>">
                <?php echo $section === 'spa' ? 'Spa' : 'Curios'; ?>
            </a>
            <?php if ($activeCategoryId !== $activeRootId): ?>
                <span>/</span><strong><?php echo h($activeCatName); ?></strong>
            <?php endif; ?>
        </div>

        <?php if ($activeChildren): ?>
            <div class="fo-category-grid mb-3">
                <?php foreach ($activeChildren as $cat): ?>
                    <a class="fo-category-button" href="front_office_pos.php?front_order_id=<?php echo (int)$orderId; ?>&property_id=<?php echo (int)$propertyId; ?>&section=<?php echo h($section); ?>&<?php echo $activeCatParam; ?>=<?php echo (int)$cat['id']; ?>">
                        <i class="bi bi-folder2-open"></i>
                        <span><?php echo h($cat['category_name']); ?></span>
                    </a>
                <?php endforeach; ?>
            </div>
        <?php endif; ?>

        <div class="fo-item-grid" id="fo_item_grid">
            <?php if (!$items): ?>
                <div class="alert alert-light border">
                    <?php if ($section === 'curio'): ?>
                        No Curio items found in inventory_items under category <?php echo fo_curio_root_category_id(); ?>.
                    <?php else: ?>
                        No Spa items found in inventory_items under category <?php echo fo_spa_root_category_id(); ?>.
                    <?php endif; ?>
                </div>
            <?php endif; ?>

            <?php foreach ($items as $it): ?>
                <?php $itemSearchText = strtolower(trim((string)($it['item_name'] ?? '') . ' ' . (string)($it['item_code'] ?? ''))); ?>
                <form method="post" class="fo-item-card" data-search="<?php echo h($itemSearchText); ?>">
                    <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                    <input type="hidden" name="action" value="add_inventory_item">
                    <input type="hidden" name="front_order_id" value="<?php echo (int)$orderId; ?>">
                    <input type="hidden" name="property_id" value="<?php echo (int)$propertyId; ?>">
                    <input type="hidden" name="section" value="<?php echo h($section); ?>">
                    <input type="hidden" name="spa_category_id" value="<?php echo (int)$spaCategoryId; ?>">
                    <input type="hidden" name="curio_category_id" value="<?php echo (int)$curioCategoryId; ?>">
                    <input type="hidden" name="inventory_item_id" value="<?php echo (int)$it['id']; ?>">

                    <div class="fo-item-name"><?php echo h($it['item_name']); ?></div>
                    <div class="fo-item-meta"><?php echo h($section === 'curio' ? 'Curios' : 'Spa'); ?><?php echo !empty($it['item_code']) ? ' • '.h($it['item_code']) : ''; ?></div>
                    <div class="fo-price"><?php echo money_fmt($it['price']); ?></div>

                    <div class="input-group input-group-sm mt-2">
                        <span class="input-group-text">Qty</span>
                        <input type="number" step="1" min="1" name="qty" class="form-control" value="1">
                        <button class="btn btn-primary" <?php echo !$order || !in_array((string)$order['status'], ['open','sent'], true) ? 'disabled' : ''; ?>>Add</button>
                    </div>
                </form>
            <?php endforeach; ?>
        </div>

        <div id="fo_no_live_results" class="alert alert-light border mt-2 d-none">No matching items on this page.</div>
    </div>

    <div class="col-xl-4 fo-pos-checkout">
        <div class="card panel-card fo-check-card">
            <div class="card-header d-flex justify-content-between align-items-center">
                <strong>Current Sale</strong>
                <?php if ($order): ?>
                    <span class="badge text-bg-<?php echo $order['status']==='closed'?'success':($order['status']==='void'?'secondary':'primary'); ?>">
                        <?php echo h(ucwords(str_replace('_',' ',(string)$order['status']))); ?>
                    </span>
                <?php endif; ?>
            </div>

            <div class="card-body">
                <?php if (!$order): ?>
                    <div class="text-center text-muted py-4">Open a sale first.</div>
                <?php else: ?>
                    <div class="fo-sale-head mb-2">
                        <div><strong><?php echo h($order['order_no']); ?></strong></div>
                        <div class="small text-muted"><?php echo h($order['guest_name'] ?: 'Walk-in Guest'); ?><?php echo !empty($order['room_no']) ? ' • Room '.h($order['room_no']) : ''; ?></div>
                    </div>

                    <div class="d-flex gap-2 mb-2">
                        <a class="btn btn-sm btn-outline-warning flex-fill" target="_blank"
                           href="front_office_pos.php?front_order_id=<?php echo (int)$order['id']; ?>&property_id=<?php echo (int)$propertyId; ?>&print_bill_preview=1">
                            <i class="bi bi-file-earmark-text me-1"></i>Preview Bill
                        </a>
                        <a class="btn btn-sm btn-warning flex-fill"
                           href="front_office_pos.php?front_order_id=<?php echo (int)$order['id']; ?>&property_id=<?php echo (int)$propertyId; ?>&printnode_bill_preview=1"
                           onclick="return confirm('Print preview bill (blank gratuity + signature) to PrintNode master printer <?php echo FO_PRINTNODE_MASTER_PRINTER_ID; ?>?');">
                            <i class="bi bi-printer-fill me-1"></i>Preview (PrintNode)
                        </a>
                    </div>

                    <details class="fo-secondary-prints">
                        <summary><i class="bi bi-printer me-1"></i>Reprint receipt options</summary>
                        <div class="d-flex gap-2 mt-2">
                            <a class="btn btn-sm btn-outline-primary flex-fill" target="_blank"
                               href="front_office_pos.php?front_order_id=<?php echo (int)$order['id']; ?>&property_id=<?php echo (int)$propertyId; ?>&print_receipt=1">
                                <i class="bi bi-printer me-1"></i>Browser Print
                            </a>
                            <a class="btn btn-sm btn-primary flex-fill"
                               href="front_office_pos.php?front_order_id=<?php echo (int)$order['id']; ?>&property_id=<?php echo (int)$propertyId; ?>&printnode_receipt=1"
                               onclick="return confirm('Send receipt to PrintNode master printer <?php echo FO_PRINTNODE_MASTER_PRINTER_ID; ?>?');">
                                <i class="bi bi-printer-fill me-1"></i>PrintNode
                            </a>
                        </div>
                    </details>

                    <div class="fo-cart-heading">
                        <div>
                            <span class="fo-cart-kicker">CART</span>
                            <strong>Sale Items</strong>
                        </div>
                        <span class="badge text-bg-light"><?php echo count(array_filter($orderLines, function($x){ return ($x['status'] ?? '') === 'active'; })); ?> item(s)</span>
                    </div>

                    <div class="fo-lines">
                        <?php if (!$orderLines): ?>
                            <div class="text-muted small py-2">No items added.</div>
                        <?php endif; ?>

                        <?php foreach ($orderLines as $ln): ?>
                            <div class="fo-line <?php echo $ln['status']==='void'?'text-muted opacity-50':''; ?>">
                                <div class="flex-grow-1">
                                    <strong><?php echo h($ln['item_name']); ?></strong>
                                    <div class="small text-muted"><?php echo money_fmt($ln['unit_price']); ?> x <?php echo money_fmt($ln['qty']); ?></div>
                                </div>

                                <div class="text-end">
                                    <div class="fw-bold"><?php echo money_fmt($ln['line_total']); ?></div>

                                    <?php if ($ln['status']==='active' && $order['status'] !== 'closed'): ?>
                                        <div class="d-flex gap-1 mt-1">
                                            <form method="post" class="d-flex gap-1">
                                                <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                                <input type="hidden" name="action" value="update_qty">
                                                <input type="hidden" name="front_order_id" value="<?php echo (int)$order['id']; ?>">
                                                <input type="hidden" name="property_id" value="<?php echo (int)$propertyId; ?>">
                                                <input type="hidden" name="item_id" value="<?php echo (int)$ln['id']; ?>">
                                                <input type="number" step="1" min="0.01" name="qty" value="<?php echo h($ln['qty']); ?>" class="form-control form-control-sm fo-qty">
                                                <button class="btn btn-sm btn-outline-secondary">OK</button>
                                            </form>

                                            <form method="post" onsubmit="return confirm('Void this item?');">
                                                <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                                <input type="hidden" name="action" value="void_item">
                                                <input type="hidden" name="front_order_id" value="<?php echo (int)$order['id']; ?>">
                                                <input type="hidden" name="property_id" value="<?php echo (int)$propertyId; ?>">
                                                <input type="hidden" name="item_id" value="<?php echo (int)$ln['id']; ?>">
                                                <button class="btn btn-sm btn-outline-danger">Void</button>
                                            </form>

                                            <button type="button" class="btn btn-sm btn-outline-primary"
                                                    onclick="document.getElementById('foItemDisc<?php echo (int)$ln['id']; ?>').classList.toggle('d-none');">
                                                Disc
                                            </button>
                                        </div>

                                        <div id="foItemDisc<?php echo (int)$ln['id']; ?>" class="d-none mt-1">
                                            <form method="post" class="d-flex flex-wrap gap-1 justify-content-end align-items-center">
                                                <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                                <input type="hidden" name="action" value="discount_item">
                                                <input type="hidden" name="front_order_id" value="<?php echo (int)$order['id']; ?>">
                                                <input type="hidden" name="property_id" value="<?php echo (int)$propertyId; ?>">
                                                <input type="hidden" name="item_id" value="<?php echo (int)$ln['id']; ?>">
                                                <select name="discount_mode" class="form-select form-select-sm" style="width:auto;">
                                                    <option value="percent">%</option>
                                                    <option value="amount">Amt</option>
                                                </select>
                                                <input type="number" step="0.01" min="0" name="discount_value" placeholder="0" class="form-control form-control-sm" style="width:70px;" required>
                                                <input type="text" name="discount_reason" placeholder="Reason (optional)" class="form-control form-control-sm" style="width:120px;">
                                                <button class="btn btn-sm btn-primary">Apply</button>
                                            </form>
                                        </div>
                                    <?php endif; ?>
                                </div>
                            </div>
                        <?php endforeach; ?>
                    </div>

                    <?php
                        /* Current active discount on this bill, if any */
                        $foActiveDiscount = 0.00;
                        $foActiveDiscountLabel = '';
                        foreach ($orderLines as $foDln) {
                            if (($foDln['status'] ?? '') === 'active' && (float)$foDln['line_total'] < 0 && strpos((string)$foDln['item_name'], 'Discount') === 0) {
                                $foActiveDiscount += -(float)$foDln['line_total'];
                                $foActiveDiscountLabel = (string)$foDln['item_name'];
                            }
                        }
                    ?>
                    <?php if (!in_array((string)$order['status'], ['closed','void'], true)): ?>
                        <div class="mt-2">
                            <a class="small text-decoration-none" data-bs-toggle="collapse" href="#foDiscountBox" role="button">
                                <i class="bi bi-percent me-1"></i><?php echo $foActiveDiscount > 0 ? 'Discount applied: '.h($foActiveDiscountLabel).' (-'.money_fmt($foActiveDiscount).') - change' : 'Add discount'; ?>
                            </a>
                            <div class="collapse mt-2" id="foDiscountBox">
                                <form method="post" class="row g-2 border rounded p-2 bg-light">
                                    <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                    <input type="hidden" name="action" value="apply_discount">
                                    <input type="hidden" name="front_order_id" value="<?php echo (int)$order['id']; ?>">
                                    <input type="hidden" name="property_id" value="<?php echo (int)$propertyId; ?>">

                                    <div class="col-4">
                                        <select name="discount_mode" class="form-select form-select-sm">
                                            <option value="percent">%</option>
                                            <option value="amount">Amount</option>
                                        </select>
                                    </div>
                                    <div class="col-8">
                                        <input type="number" step="0.01" min="0.01" name="discount_value" class="form-control form-control-sm" placeholder="Discount value" required>
                                    </div>
                                    <div class="col-12">
                                        <input type="text" name="discount_reason" class="form-control form-control-sm" placeholder="Reason (required, kept on bill audit)" required>
                                    </div>
                                    <div class="col-12 d-flex gap-1">
                                        <button class="btn btn-sm btn-outline-primary flex-grow-1" onclick="return confirm('Apply this discount to the bill?');">Apply Discount</button>
                                    </div>
                                    <div class="col-12 small text-muted">Applying again replaces the current discount - it never stacks. Percent is calculated on the bill total before any discount.</div>
                                </form>
                                <?php if ($foActiveDiscount > 0): ?>
                                    <form method="post" class="mt-1" onsubmit="return confirm('Remove the discount from this bill?');">
                                        <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                        <input type="hidden" name="action" value="remove_discount">
                                        <input type="hidden" name="front_order_id" value="<?php echo (int)$order['id']; ?>">
                                        <input type="hidden" name="property_id" value="<?php echo (int)$propertyId; ?>">
                                        <button class="btn btn-sm btn-outline-danger w-100">Remove Discount (-<?php echo money_fmt($foActiveDiscount); ?>)</button>
                                    </form>
                                <?php endif; ?>
                            </div>
                        </div>
                    <?php endif; ?>

                    <div class="fo-total-box mt-3">
                        <div><span>Subtotal</span><strong><?php echo money_fmt($order['subtotal']); ?></strong></div>
                        <div><span>Tax</span><strong><?php echo money_fmt($order['tax_total']); ?></strong></div>
                        <div class="total"><span>Total</span><strong><?php echo money_fmt($order['total_amount']); ?></strong></div>
                    </div>

                    <?php if ($payments): ?>
                        <div class="mt-3 small">
                            <strong>Payments</strong>
                            <?php foreach ($payments as $pay): ?>
                                <div class="d-flex justify-content-between border-bottom py-1">
                                    <span><?php echo h($pay['payment_method']); ?></span>
                                    <strong><?php echo money_fmt($pay['amount']); ?></strong>
                                </div>
                            <?php endforeach; ?>
                        </div>
                    <?php endif; ?>

                    <?php if (!in_array((string)$order['status'], ['closed','void'], true)): ?>
                        <?php $isFolioSale = !empty($order['linked_order_id']); ?>
                        <div class="fo-sticky-actions">
                        <div class="accordion mt-3 fo-payment-workspace" id="foCloseAcc">
                            <div class="accordion-item">
                                <h2 class="accordion-header">
                                    <button class="accordion-button <?php echo $isFolioSale ? 'collapsed' : ''; ?>" type="button" data-bs-toggle="collapse" data-bs-target="#payCashCard">Close with Payment</button>
                                </h2>
                                <div id="payCashCard" class="accordion-collapse collapse <?php echo $isFolioSale ? '' : 'show'; ?>" data-bs-parent="#foCloseAcc">
                                    <div class="accordion-body">
                                        <form method="post" class="row g-2">
                                            <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                            <input type="hidden" name="action" value="close_payment">
                                            <input type="hidden" name="front_order_id" value="<?php echo (int)$order['id']; ?>">
                                            <input type="hidden" name="property_id" value="<?php echo (int)$propertyId; ?>">

                                            <div class="col-6">
                                                <select name="payment_method" id="fo_payment_method" class="form-select form-select-sm">
                                                    <option value="Cash">Cash</option>
                                                    <option value="Card">Card</option>
                                                    <option value="POS">POS</option>
                                                    <option value="Bank Transfer">Bank Transfer</option>
                                                    <option value="Mobile Money">Mobile Money</option>
                                                </select>
                                            </div>

                                            <div class="col-6">
                                                <label class="form-label small mb-1">Purchase + gratuity before surcharge</label>
                                                <input type="number" step="0.01" name="payment_amount" id="fo_payment_amount" class="form-control form-control-sm" value="<?php echo h($order['total_amount']); ?>" readonly>
                                            </div>

                                            <div class="col-6">
                                                <label class="form-label small mb-1">3% Card/POS Surcharge</label>
                                                <input type="text" id="fo_surcharge_display" class="form-control form-control-sm" value="0.00" readonly>
                                            </div>

                                            <div class="col-6">
                                                <label class="form-label small mb-1">Total to collect</label>
                                                <input type="text" id="fo_total_collect_display" class="form-control form-control-sm fw-bold" value="<?php echo h($order['total_amount']); ?>" readonly>
                                            </div>

                                            <div class="col-6">
                                                <label class="form-label small mb-1">Gratuity</label>
                                                <input type="number" step="0.01" min="0" name="gratuity_amount" id="fo_gratuity_amount" class="form-control form-control-sm" value="0.00" data-base-total="<?php echo h($order['total_amount']); ?>">
                                                <div class="form-text small">Inventory item #<?php echo (int)FO_GRATUITY_INVENTORY_ITEM_ID; ?>. No 3% surcharge.</div>
                                            </div>

                                            <div class="col-12">
                                                <input type="text" name="reference_no" class="form-control form-control-sm" placeholder="Reference / auth code">
                                            </div>

                                            <div class="col-12 small text-muted">Card/POS adds automatic 3% surcharge on the purchase amount only. Gratuity is added as its own receipt line and is never surcharged.</div>
                                            <div class="col-12 d-grid"><button class="btn btn-success btn-sm">Post Payment</button></div>
                                        </form>
                                    </div>
                                </div>
                            </div>

                            <div class="accordion-item">
                                <h2 class="accordion-header">
                                    <button class="accordion-button <?php echo $isFolioSale ? '' : 'collapsed'; ?>" type="button" data-bs-toggle="collapse" data-bs-target="#postFolio">Post to PMS Folio / Room<?php echo $isFolioSale ? ' <span class="badge bg-primary ms-1">Recommended for this sale</span>' : ''; ?></button>
                                </h2>
                                <div id="postFolio" class="accordion-collapse collapse <?php echo $isFolioSale ? 'show' : ''; ?>" data-bs-parent="#foCloseAcc">
                                    <div class="accordion-body">
                                        <form method="post" class="row g-2" id="fo_post_folio_form">
                                            <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                            <input type="hidden" name="action" value="post_to_folio">
                                            <input type="hidden" name="front_order_id" value="<?php echo (int)$order['id']; ?>">
                                            <input type="hidden" name="property_id" value="<?php echo (int)$propertyId; ?>">

                                            <div class="col-12">
                                                <?php $orderFoliosForSelect = fo_fetch_folios_for_order($pdo, (int)($order['linked_order_id'] ?? 0)); ?>
                                                <select name="folio_id" id="fo_close_folio_select" class="form-select form-select-sm" required>
                                                    <option value="">Select active Room / Guest folio</option>
                                                    <?php foreach ($orderFoliosForSelect as $fo): ?>
                                                        <option value="<?php echo (int)$fo['id']; ?>" data-folio-type="<?php echo h(strtolower((string)$fo['folio_type'])); ?>" <?php echo (int)($order['linked_folio_id'] ?? 0) === (int)$fo['id'] ? 'selected' : ''; ?>>
                                                            <?php echo h(strtoupper($fo['folio_type']).' • '.$fo['folio_no'].' • '.$fo['folio_name']); ?>
                                                        </option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>

                                            <div class="col-12 d-grid"><button type="submit" class="btn btn-primary btn-sm">Post Charge to Folio</button></div>

                                            <?php if (empty($order['linked_order_id'])): ?>
                                                <div class="col-12 small text-warning">This sale was not opened from an in-house booking, so no folios are listed.</div>
                                            <?php elseif (!$orderFoliosForSelect): ?>
                                                <div class="col-12 small text-warning">No active Room or Guest folios were found for this booking yet.</div>
                                            <?php endif; ?>
                                        </form>
                                    </div>
                                </div>
                            </div>

                            <div class="accordion-item">
                                <h2 class="accordion-header">
                                    <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#postCostCentre">Post to Cost Centre (Internal Account)</button>
                                </h2>
                                <div id="postCostCentre" class="accordion-collapse collapse" data-bs-parent="#foCloseAcc">
                                    <div class="accordion-body">
                                        <?php $foCostCentres = function_exists('r_load_cost_centers') ? r_load_cost_centers($pdo) : []; ?>
                                        <?php if (!$foCostCentres): ?>
                                            <div class="small text-warning">No active cost centres found.</div>
                                        <?php else: ?>
                                        <form method="post" class="row g-2" onsubmit="return confirm('Post this sale to the selected cost centre? This closes the sale.');">
                                            <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                            <input type="hidden" name="action" value="post_to_cost_center">
                                            <input type="hidden" name="front_order_id" value="<?php echo (int)$order['id']; ?>">
                                            <input type="hidden" name="property_id" value="<?php echo (int)$propertyId; ?>">

                                            <div class="col-12">
                                                <label class="form-label small mb-1">Cost centre</label>
                                                <select name="cost_center_id" class="form-select form-select-sm" required>
                                                    <option value="">Select cost centre</option>
                                                    <?php foreach ($foCostCentres as $cc): ?>
                                                        <option value="<?php echo (int)$cc['id']; ?>"><?php echo h($cc['name']); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>

                                            <div class="col-12">
                                                <label class="form-label small mb-1">Reason <span class="text-danger">*</span></label>
                                                <input type="text" name="cost_center_reason" class="form-control form-control-sm" placeholder="e.g. Management lunch" required>
                                            </div>

                                            <div class="col-12">
                                                <input type="text" name="cost_center_note" class="form-control form-control-sm" placeholder="Note (optional)">
                                            </div>

                                            <div class="col-6">
                                                <label class="form-label small mb-1">Discount</label>
                                                <select name="cc_discount_mode" class="form-select form-select-sm">
                                                    <option value="none">None</option>
                                                    <option value="percent">Percent %</option>
                                                    <option value="amount">Amount</option>
                                                </select>
                                            </div>
                                            <div class="col-6">
                                                <label class="form-label small mb-1">Discount value</label>
                                                <input type="number" step="0.01" min="0" name="cc_discount_value" class="form-control form-control-sm" value="0">
                                            </div>

                                            <div class="col-12">
                                                <details>
                                                    <summary class="small text-muted">Post only selected items (optional)</summary>
                                                    <div class="mt-1">
                                                        <?php foreach ($orderLines as $ln): if (($ln['status'] ?? '') !== 'active' || (float)$ln['line_total'] <= 0) continue; ?>
                                                            <div class="form-check">
                                                                <input class="form-check-input" type="checkbox" name="cost_center_item_ids[]" value="<?php echo (int)$ln['id']; ?>" id="ccItem<?php echo (int)$ln['id']; ?>">
                                                                <label class="form-check-label small" for="ccItem<?php echo (int)$ln['id']; ?>">
                                                                    <?php echo h($ln['item_name']); ?> — <?php echo money_fmt($ln['line_total']); ?>
                                                                </label>
                                                            </div>
                                                        <?php endforeach; ?>
                                                        <div class="form-text small">Leave all unticked to post the full bill.</div>
                                                    </div>
                                                </details>
                                            </div>

                                            <div class="col-12">
                                                <label class="form-label small mb-1">Supervisor PIN <span class="text-danger">*</span></label>
                                                <input type="password" name="supervisor_pin" class="form-control form-control-sm" placeholder="Supervisor PIN" required autocomplete="off">
                                            </div>

                                            <div class="col-12 d-grid"><button type="submit" class="btn btn-dark btn-sm">Post to Cost Centre</button></div>
                                        </form>
                                        <?php endif; ?>
                                    </div>
                                </div>
                            </div>
                        </div>
                        </div>

                        <?php if (!$orderLines): ?>
                            <form method="post" class="mt-3" onsubmit="return confirm('Cancel this empty sale?');">
                                <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                <input type="hidden" name="action" value="cancel_empty_sale">
                                <input type="hidden" name="front_order_id" value="<?php echo (int)$order['id']; ?>">
                                <input type="hidden" name="property_id" value="<?php echo (int)$propertyId; ?>">
                                <button class="btn btn-outline-danger btn-sm w-100">Cancel Empty Sale</button>
                            </form>
                        <?php endif; ?>
                    <?php else: ?>
                        <div class="alert alert-success mt-3 py-2">Sale is closed.</div>

                        <div class="card mt-2 border-warning">
                            <div class="card-header py-2 bg-warning-subtle"><strong><i class="bi bi-arrow-counterclockwise me-1"></i>Reverse Closed Sale</strong></div>
                            <div class="card-body">
                                <p class="small text-muted mb-2">Requires a supervisor PIN. <strong>Void</strong> fully reverses the sale (payments and any folio posting). <strong>Reopen</strong> unlocks it for editing.</p>
                                <form method="post" onsubmit="return confirm('Reverse this closed sale?');">
                                    <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                                    <input type="hidden" name="action" value="reverse_closed_sale">
                                    <input type="hidden" name="front_order_id" value="<?php echo (int)$order['id']; ?>">
                                    <input type="hidden" name="property_id" value="<?php echo (int)$propertyId; ?>">

                                    <div class="mb-2">
                                        <label class="form-label small mb-1">Action</label>
                                        <select name="reverse_mode" id="fo_reverse_mode" class="form-select form-select-sm">
                                            <option value="reopen">Reopen for editing</option>
                                            <option value="void">Void (full reversal)</option>
                                        </select>
                                    </div>
                                    <div class="mb-2">
                                        <input type="text" name="reverse_reason" class="form-control form-control-sm" placeholder="Reason (optional)">
                                    </div>
                                    <div class="mb-2">
                                        <input type="password" name="manager_pin" class="form-control form-control-sm" placeholder="Supervisor PIN" required autocomplete="off">
                                    </div>
                                    <div class="d-grid"><button type="submit" id="fo_reverse_btn" class="btn btn-outline-danger btn-sm">Reopen Sale for Editing</button></div>
                                </form>
                                <script>
                                (function(){
                                    var sel = document.getElementById('fo_reverse_mode');
                                    var btn = document.getElementById('fo_reverse_btn');
                                    if (sel && btn) {
                                        var upd = function(){
                                            btn.textContent = (sel.value === 'void') ? 'Void Sale (Full Reversal)' : 'Reopen Sale for Editing';
                                            btn.classList.toggle('btn-danger', sel.value === 'void');
                                            btn.classList.toggle('btn-outline-danger', sel.value !== 'void');
                                        };
                                        sel.addEventListener('change', upd);
                                        upd();
                                    }
                                })();
                                </script>
                            </div>
                        </div>
                    <?php endif; ?>
                <?php endif; ?>
            </div>
        </div>
    </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function () {
    const saleType = document.getElementById('sale_type');
    const blocks   = document.querySelectorAll('.fo-folio-block');
    const pmsOrder = document.getElementById('pms_order_id');
    const folio    = document.getElementById('folio_id');
    const guest    = document.getElementById('guest_name');
    const room     = document.getElementById('room_no');
    const foliosByOrder = <?php
        $folioMap = [];
        foreach ($inhouseBookings as $b) {
            $folioMap[(int)$b['id']] = fo_fetch_folios_for_order($pdo, (int)$b['id']);
        }
        echo json_encode($folioMap, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    ?>;

    function toggleType() {
        const isFolio = saleType && saleType.value === 'folio';
        blocks.forEach(el => el.classList.toggle('d-none', !isFolio));

        if (guest) {
            guest.placeholder = isFolio ? 'Auto-filled from selected in-house booking' : 'Walk-in Guest';
            guest.readOnly = !!isFolio;
            if (!isFolio && guest.value.trim() === 'In-house Guest') guest.value = '';
        }

        if (!isFolio && room) room.value = '';
        if (isFolio) loadFolios();
    }

    function loadFolios() {
        if (!pmsOrder || !folio) return;

        const id  = String(pmsOrder.value || '0');
        const opt = pmsOrder.options[pmsOrder.selectedIndex];

        if (guest) guest.value = (opt && opt.dataset.guest ? opt.dataset.guest : '');
        if (room)  room.value  = (opt && opt.dataset.room  ? opt.dataset.room  : '');

        folio.innerHTML = '<option value="0">Select folio</option>';

        (foliosByOrder[id] || []).forEach(function (f) {
            const folioType = String(f.folio_type || '').toLowerCase().trim();
            if (folioType === 'master') return;

            const o = document.createElement('option');
            o.value = f.id;
            o.dataset.folioType = folioType;
            o.textContent = folioType.toUpperCase() + ' • ' + (f.folio_no || '') + ' • ' + (f.folio_name || '');
            folio.appendChild(o);
        });
    }

    if (saleType) {
        saleType.addEventListener('change', toggleType);
        toggleType();
    }
    if (pmsOrder) {
        pmsOrder.addEventListener('change', loadFolios);
    }
    const closeFolioSelect = document.getElementById('fo_close_folio_select');
    const postFolioForm    = document.getElementById('fo_post_folio_form');

    /*
       Master Folios are not supplied by the backend, but keep a final browser
       guard for stale cached markup or manually altered DOM.
    */
    if (postFolioForm) {
        postFolioForm.addEventListener('submit', function (e) {
            if (!closeFolioSelect || !closeFolioSelect.value) {
                e.preventDefault();
                alert('Select an active Room or Guest folio.');
                return;
            }
            const opt = closeFolioSelect.options[closeFolioSelect.selectedIndex];
            if (opt && String(opt.dataset.folioType || '').toLowerCase() === 'master') {
                e.preventDefault();
                alert('Master Folios are excluded from Room posting. Select a Room or Guest folio.');
            }
        });
    }

    const liveSearch = document.getElementById('fo_live_search');
    const noLiveResults = document.getElementById('fo_no_live_results');

    function filterVisibleItems() {
        if (!liveSearch) return;

        const term = liveSearch.value.toLowerCase().trim();
        let visibleCount = 0;

        document.querySelectorAll('.fo-item-card').forEach(function (card) {
            const text = card.getAttribute('data-search') || '';
            const match = term === '' || text.includes(term);

            card.classList.toggle('d-none', !match);
            if (match) visibleCount++;
        });

        if (noLiveResults) {
            noLiveResults.classList.toggle('d-none', visibleCount > 0);
        }
    }

    if (liveSearch) {
        liveSearch.addEventListener('input', filterVisibleItems);
        filterVisibleItems();
    }

    const gratuityInput = document.getElementById('fo_gratuity_amount');
    const paymentInput = document.getElementById('fo_payment_amount');
    const paymentMethod = document.getElementById('fo_payment_method');
    const surchargeDisplay = document.getElementById('fo_surcharge_display');
    const totalCollectDisplay = document.getElementById('fo_total_collect_display');

    function isCardOrPosMethod(method) {
        method = String(method || '').toLowerCase().trim();
        return ['pos','card','credit card','debit card','visa','mastercard','pos/card','card payment'].includes(method);
    }

    function refreshPaymentAmountWithGratuity() {
        if (!gratuityInput || !paymentInput) return;
        const baseTotal = parseFloat(gratuityInput.dataset.baseTotal || '0') || 0;
        const gratuity = Math.max(0, parseFloat(gratuityInput.value || '0') || 0);
        const beforeSurcharge = baseTotal + gratuity;
        const surcharge = (paymentMethod && isCardOrPosMethod(paymentMethod.value)) ? (baseTotal * 0.03) : 0;
        const totalToCollect = beforeSurcharge + surcharge;

        paymentInput.value = beforeSurcharge.toFixed(2);
        if (surchargeDisplay) surchargeDisplay.value = surcharge.toFixed(2);
        if (totalCollectDisplay) totalCollectDisplay.value = totalToCollect.toFixed(2);
    }

    if (gratuityInput) {
        gratuityInput.addEventListener('input', refreshPaymentAmountWithGratuity);
        gratuityInput.addEventListener('change', refreshPaymentAmountWithGratuity);
    }
    if (paymentMethod) {
        paymentMethod.addEventListener('change', refreshPaymentAmountWithGratuity);
    }
    refreshPaymentAmountWithGratuity();
});
</script>

<style>
:root{
    --fo-navy:#0f1f38;
    --fo-navy-2:#152a4a;
    --fo-blue:#2563eb;
    --fo-blue-soft:#eaf1ff;
    --fo-cyan:#0ea5e9;
    --fo-bg:#eef3f9;
    --fo-card:#ffffff;
    --fo-border:#dbe5f0;
    --fo-muted:#6b7a90;
    --fo-text:#172033;
    --fo-success:#0f9f6e;
    --fo-shadow:0 18px 45px rgba(15,31,56,.10);
}
body{background:var(--fo-bg)!important;color:var(--fo-text)}
main,.main-content,.content-wrapper{background:transparent!important}
.container,.container-fluid{max-width:100%!important}

.fo-pos-hero{
    padding:18px 22px!important;border-radius:20px!important;
    background:linear-gradient(135deg,var(--fo-navy),var(--fo-navy-2))!important;
    border:1px solid rgba(255,255,255,.08);box-shadow:0 18px 45px rgba(15,31,56,.24);
    margin-bottom:18px!important;position:relative;overflow:hidden
}
.fo-pos-hero:after{content:"";position:absolute;width:260px;height:260px;border-radius:50%;right:-85px;top:-155px;background:rgba(255,255,255,.07)}
.fo-pos-hero .page-eyebrow{font-size:.72rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase}
.fo-pos-hero .hero-title{font-size:1.55rem;line-height:1.15;font-weight:900;letter-spacing:-.02em}
.fo-pos-hero .hero-subtitle{font-size:.87rem;line-height:1.35;color:rgba(255,255,255,.72)!important;margin-top:3px}
.fo-pos-hero .btn{border-radius:10px;font-weight:800;padding:.52rem .85rem}

.fo-pos-workspace{align-items:flex-start;margin-bottom:26px}
.fo-pos-workspace>.fo-pos-sidebar{width:18%}
.fo-pos-workspace>.fo-pos-catalogue{width:34%}
.fo-pos-workspace>.fo-pos-checkout{width:48%}

.panel-card,.fo-browser-card,.fo-check-card{
    border:1px solid var(--fo-border)!important;border-radius:18px!important;
    box-shadow:var(--fo-shadow)!important;background:var(--fo-card)!important;overflow:hidden
}
.panel-card .card-header,.fo-check-card .card-header{
    background:#fff!important;border-bottom:1px solid var(--fo-border)!important;
    padding:14px 16px!important;color:var(--fo-navy);font-size:.86rem;letter-spacing:.01em
}
.panel-card .card-body,.fo-browser-card .card-body,.fo-check-card .card-body{padding:15px!important}
.form-label{font-size:.72rem;font-weight:900;text-transform:uppercase;letter-spacing:.055em;color:#5d6b80;margin-bottom:5px}
.form-control,.form-select,.input-group-text{
    min-height:38px;border-radius:10px!important;border-color:#ccd8e6!important;font-size:.84rem!important
}
.form-control:focus,.form-select:focus{border-color:#6ea0ff!important;box-shadow:0 0 0 3px rgba(37,99,235,.12)!important}
.input-group>.form-control,.input-group>.form-select{border-radius:0!important}
.input-group>.input-group-text:first-child{border-radius:10px 0 0 10px!important}
.input-group>.btn:last-child{border-radius:0 10px 10px 0!important}
.btn{font-weight:800;border-radius:10px}
.btn-primary{background:var(--fo-blue);border-color:var(--fo-blue)}
.alert{border-radius:13px;border-width:1px}

/* Left operational rail */
.fo-pos-sidebar .panel-card{margin-bottom:14px!important}
.fo-pos-sidebar .list-group-item{border-color:#e8eef5;padding:.72rem .8rem}
.fo-pos-sidebar .list-group-item-action{transition:.18s ease}
.fo-pos-sidebar .list-group-item-action:hover{background:#f3f7ff;transform:translateX(2px)}
.fo-pos-sidebar .list-group-item.active{background:linear-gradient(135deg,#1e4fa5,#2563eb);border-color:#2563eb}
.fo-closed-sales-card{max-height:calc(100vh - 505px);min-height:280px}
.fo-closed-sales-list{max-height:335px;overflow:auto}
.fo-closed-sales-list .list-group-item{padding:.65rem .75rem}
.fo-closed-sales-card .form-control{font-size:.76rem}

/* Product catalogue workspace */
.fo-browser-card{position:sticky;top:72px;z-index:3;margin-bottom:14px!important}
.fo-browser-card .card-body{background:linear-gradient(180deg,#fff,#fbfdff)}
.fo-browser-card .btn{min-width:105px}
.fo-breadcrumbs{font-size:.76rem;color:var(--fo-muted);display:flex;gap:7px;align-items:center;flex-wrap:wrap;padding:0 2px}
.fo-breadcrumbs a{text-decoration:none;font-weight:900;color:var(--fo-blue)}
.fo-category-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:11px}
.fo-category-button{
    border:1px solid #cfe0f6;background:#fff;border-radius:14px;padding:13px 14px;text-decoration:none;
    color:var(--fo-navy);font-weight:900;display:flex;gap:9px;align-items:center;
    box-shadow:0 8px 18px rgba(15,31,56,.05);transition:.18s ease
}
.fo-category-button i{color:var(--fo-blue);font-size:1rem}
.fo-category-button:hover{border-color:#79a8f8;transform:translateY(-2px);color:#17499d;box-shadow:0 12px 24px rgba(37,99,235,.12)}
.fo-item-grid{
    display:grid;grid-template-columns:repeat(auto-fill,minmax(185px,1fr));gap:12px;
    max-height:calc(100vh - 315px);overflow:auto;padding:2px 4px 16px 2px
}
.fo-item-card{
    border:1px solid #dfe8f2;border-radius:16px;background:#fff;padding:14px;
    box-shadow:0 8px 22px rgba(15,31,56,.055);transition:.18s ease;position:relative
}
.fo-item-card:before{content:"";position:absolute;left:0;top:15px;bottom:15px;width:3px;border-radius:3px;background:#d9e7fb}
.fo-item-card:hover{transform:translateY(-2px);border-color:#8ab2f5;box-shadow:0 15px 30px rgba(37,99,235,.12)}
.fo-item-name{font-weight:900;color:var(--fo-navy);line-height:1.2;padding-left:3px;min-height:38px}
.fo-item-meta{font-size:.69rem;color:var(--fo-muted);margin-top:4px;min-height:18px;padding-left:3px;text-transform:uppercase;letter-spacing:.035em}
.fo-price{font-size:1.17rem;font-weight:950;color:var(--fo-success);margin-top:9px;padding-left:3px}
.fo-item-card .input-group{box-shadow:0 5px 12px rgba(15,31,56,.05);border-radius:10px}

/* Checkout / current sale */
.fo-check-card{position:sticky;top:72px;display:flex;flex-direction:column;min-height:calc(100vh - 90px);max-height:none}
.fo-check-card>.card-header{flex:0 0 auto;background:linear-gradient(180deg,#fff,#f8fbff)!important}
.fo-check-card>.card-body{flex:1 1 auto;overflow:visible;display:flex;flex-direction:column;min-height:0}
.fo-sale-head{border:1px solid #d8e5f3;background:linear-gradient(135deg,#f7faff,#eef5ff);border-radius:14px;padding:12px 13px}
.fo-sale-head strong{font-size:1rem;color:var(--fo-navy)}
.fo-secondary-prints{margin-bottom:10px;border:1px solid #e2eaf3;border-radius:11px;padding:8px 10px;background:#fafcff}
.fo-secondary-prints summary{cursor:pointer;font-size:.76rem;color:var(--fo-blue);font-weight:900;list-style:none}
.fo-secondary-prints summary::-webkit-details-marker{display:none}
.fo-cart-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:14px 0 8px;padding:0 2px}
.fo-cart-heading>div{display:flex;align-items:center;gap:9px}
.fo-cart-heading strong{font-size:.94rem;color:var(--fo-navy)}
.fo-cart-kicker{font-size:.65rem;font-weight:950;letter-spacing:.12em;color:var(--fo-blue);background:#edf4ff;border:1px solid #cfe0fb;border-radius:999px;padding:4px 8px}
.fo-lines{display:flex;flex-direction:column;gap:9px;min-height:245px;max-height:42vh;overflow:auto;padding:4px 5px 10px 2px;border:1px solid #dbe6f2;border-radius:15px;background:#f7faff}
.fo-line{display:flex;justify-content:space-between;gap:14px;border:1px solid #d7e3f0;border-radius:13px;padding:12px 13px;background:#fff;box-shadow:0 5px 14px rgba(15,31,56,.05)}
.fo-line strong{color:var(--fo-navy);font-size:.82rem}
.fo-qty{width:72px;min-height:31px!important}
.fo-total-box{border-radius:15px;background:linear-gradient(145deg,var(--fo-navy),#1d3558);color:#fff;padding:14px 15px;box-shadow:0 15px 30px rgba(15,31,56,.20)}
.fo-total-box div{display:flex;justify-content:space-between;padding:4px 0;font-size:.82rem}
.fo-total-box .total{border-top:1px solid rgba(255,255,255,.2);margin-top:5px;padding-top:10px;font-size:1.2rem;font-weight:950}
.fo-sticky-actions{position:static;background:#fff;padding-top:14px;margin-top:14px;border-top:1px solid #dbe5f0;z-index:1;box-shadow:none}
.fo-sticky-actions .btn{min-height:40px}
.badge{border-radius:999px;padding:.45em .7em;font-weight:900;letter-spacing:.02em}
.min-w-0{min-width:0}


/* Broad checkout workspace: cart first, payment second. */
.fo-payment-workspace{display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:start}
.fo-payment-workspace .accordion-item{border:1px solid #dbe5f0!important;border-radius:14px!important;overflow:hidden;background:#fff}
.fo-payment-workspace .accordion-item+.accordion-item{margin-top:0}
.fo-payment-workspace .accordion-button{font-weight:900;color:var(--fo-navy);background:#f7faff;padding:13px 14px}
.fo-payment-workspace .accordion-collapse{display:block!important;height:auto!important;visibility:visible!important}
.fo-payment-workspace .accordion-button:after{display:none}
.fo-payment-workspace .accordion-body{padding:14px}
@media (max-width:1499.98px){.fo-payment-workspace{grid-template-columns:1fr}}

/* Scrollbars */
.fo-item-grid::-webkit-scrollbar,.fo-lines::-webkit-scrollbar,.fo-closed-sales-list::-webkit-scrollbar,.fo-check-card>.card-body::-webkit-scrollbar{width:8px}
.fo-item-grid::-webkit-scrollbar-thumb,.fo-lines::-webkit-scrollbar-thumb,.fo-closed-sales-list::-webkit-scrollbar-thumb,.fo-check-card>.card-body::-webkit-scrollbar-thumb{background:#c5d2e2;border-radius:10px}

@media (max-width:1399.98px){
    .fo-pos-workspace>.fo-pos-sidebar{width:18%}
    .fo-pos-workspace>.fo-pos-catalogue{width:32%}
    .fo-pos-workspace>.fo-pos-checkout{width:50%}
    .fo-item-grid{grid-template-columns:repeat(auto-fill,minmax(165px,1fr))}
}
@media (max-width:1199.98px){
    .fo-pos-workspace>.fo-pos-sidebar,.fo-pos-workspace>.fo-pos-catalogue,.fo-pos-workspace>.fo-pos-checkout{width:100%}
    .fo-browser-card,.fo-check-card{position:static;max-height:none}
    .fo-item-grid{max-height:none}
    .fo-sticky-actions{position:static;box-shadow:none}
    .fo-closed-sales-card{max-height:none}
}
@media (max-width:767.98px){
    .fo-pos-hero{padding:15px!important;border-radius:15px!important}
    .fo-pos-hero .hero-title{font-size:1.25rem}
    .fo-category-grid,.fo-item-grid{grid-template-columns:1fr 1fr}
}
@media (max-width:480px){.fo-category-grid,.fo-item-grid{grid-template-columns:1fr}}
</style>
<?php if ($order): require_once __DIR__ . '/thermal_print_buttons.php'; endif; ?>
<?php require_once __DIR__ . '/../../includes/footer.php'; ?>
