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

$pageTitle = 'GRN / Purchases';

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('grn_table_exists')) {
    function grn_table_exists(PDO $pdo, string $tableName): bool {
        $stmt = $pdo->prepare("
            SELECT COUNT(*)
            FROM information_schema.tables
            WHERE table_schema = DATABASE()
              AND table_name = :table_name
        ");
        $stmt->execute([':table_name' => $tableName]);
        return (int)$stmt->fetchColumn() > 0;
    }
}
if (!function_exists('grn_column_exists')) {
    function grn_column_exists(PDO $pdo, string $tableName, string $columnName): bool {
        $stmt = $pdo->prepare("
            SELECT COUNT(*)
            FROM information_schema.columns
            WHERE table_schema = DATABASE()
              AND table_name = :table_name
              AND column_name = :column_name
        ");
        $stmt->execute([':table_name' => $tableName, ':column_name' => $columnName]);
        return (int)$stmt->fetchColumn() > 0;
    }
}
if (!function_exists('grn_flash')) {
    function grn_flash(string $type, string $message): void {
        $_SESSION['grn_flash'][] = ['type' => $type, 'message' => $message];
    }
}
if (!function_exists('grn_get_flash')) {
    function grn_get_flash(): array {
        $items = $_SESSION['grn_flash'] ?? [];
        unset($_SESSION['grn_flash']);
        return $items;
    }
}
if (!function_exists('grn_no')) {
    function grn_no(PDO $pdo): string {
        $prefix = 'GRN-' . date('ymd') . '-';
        $stmt = $pdo->prepare("SELECT COUNT(*) FROM inventory_grns WHERE grn_no LIKE :p");
        $stmt->execute([':p' => $prefix . '%']);
        return $prefix . str_pad((string)((int)$stmt->fetchColumn() + 1), 4, '0', STR_PAD_LEFT);
    }
}
if (!function_exists('grn_calc_line_amounts')) {
    /*
       $grossOverride: when the cashier enters the LINE AMOUNT straight off the
       supplier invoice (instead of the unit price), that amount is used as the
       exact gross for the line rather than round(qty x unit price, 2). This
       exists because suppliers print unit prices rounded to 2dp while the true
       unit price has more decimals - qty x printed-price is then a few cents
       off the invoice line amount. Honouring the entered amount makes the GRN
       total match the supplier invoice to the cent; the unit price becomes a
       derived value (gross / qty).
    */
    function grn_calc_line_amounts(float $qty, float $unitPriceInput, string $taxType, float $taxRate, string $priceTaxMode, ?float $grossOverride = null): array {
        $qty = max(0, $qty);
        $unitPriceInput = max(0, $unitPriceInput);
        $grossInput = $grossOverride !== null
            ? round(max(0, $grossOverride), 2)
            : round($qty * $unitPriceInput, 2);
        $taxType = $taxType === 'Fixed' ? 'Fixed' : 'Percentage';
        $priceTaxMode = $priceTaxMode === 'inclusive' ? 'inclusive' : 'exclusive';

        if ($taxRate <= 0) {
            return [
                'net' => $grossInput,
                'tax' => 0.0,
                'total' => $grossInput,
                'stock_unit_cost' => $qty > 0 ? round($grossInput / $qty, 4) : 0.0,
            ];
        }

        if ($priceTaxMode === 'inclusive') {
            if ($taxType === 'Fixed') {
                $tax = round($taxRate * $qty, 2);
                $net = max(0, round($grossInput - $tax, 2));
                $total = $grossInput;
            } else {
                $tax = round($grossInput * ($taxRate / (100 + $taxRate)), 2);
                $net = round($grossInput - $tax, 2);
                $total = $grossInput;
            }
        } else {
            $net = $grossInput;
            $tax = $taxType === 'Fixed' ? round($taxRate * $qty, 2) : round($net * ($taxRate / 100), 2);
            $total = round($net + $tax, 2);
        }

        return [
            'net' => $net,
            'tax' => $tax,
            'total' => $total,
            'stock_unit_cost' => $qty > 0 ? round($net / $qty, 4) : 0.0,
        ];
    }
}

if (!function_exists('grn_post_array')) {
    function grn_post_array(string $key): array {
        $value = $_POST[$key] ?? [];
        if (!is_array($value)) {
            return [$value];
        }
        return array_values($value);
    }
}

if (!function_exists('grn_float_input')) {
    function grn_float_input($value): float {
        if ($value === null) return 0.0;
        $value = trim(str_replace(',', '', (string)$value));
        if ($value === '') return 0.0;
        return is_numeric($value) ? (float)$value : 0.0;
    }
}

if (!function_exists('grn_int_input')) {
    function grn_int_input($value): int {
        if ($value === null) return 0;
        $value = trim((string)$value);
        return is_numeric($value) ? (int)$value : 0;
    }
}

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

if (!function_exists('grn_download_filename')) {
    function grn_download_filename(string $prefix, string $ext): string {
        return $prefix . '_' . date('Ymd_His') . '.' . $ext;
    }
}
if (!function_exists('grn_recalc_avg_cost')) {
    function grn_recalc_avg_cost(PDO $pdo, int $itemId): void {
        if (!grn_table_exists($pdo, 'inventory_items')) return;
        if (!grn_column_exists($pdo, 'inventory_items', 'avg_cost')) return;

        $stmt = $pdo->prepare("
            SELECT
                COALESCE(SUM(qty_in),0) AS qty,
                COALESCE(SUM(qty_in * unit_cost),0) AS cost
            FROM stock_movements
            WHERE item_id = :item_id
              AND qty_in > 0
              AND movement_type IN ('OPENING','GRN','TRANSFER_IN','ADJUSTMENT_IN','RETURN')
        ");
        $stmt->execute([':item_id' => $itemId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        $qty = (float)($row['qty'] ?? 0);
        $cost = (float)($row['cost'] ?? 0);

        if ($qty > 0) {
            $avg = $cost / $qty;
            $stmt = $pdo->prepare("
                UPDATE inventory_items
                SET avg_cost = :avg_cost,
                    cost_price = CASE WHEN cost_price = 0 THEN :avg_cost_for_cost_price ELSE cost_price END
                WHERE id = :id
            ");
            $stmt->execute([
                ':avg_cost' => $avg,
                ':avg_cost_for_cost_price' => $avg,
                ':id' => $itemId,
            ]);
        }
    }
}

$errors = [];

try {
    $pdo->exec("CREATE TABLE IF NOT EXISTS store_locations (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        location_code VARCHAR(50) NULL,
        location_name VARCHAR(150) NOT NULL,
        location_type ENUM('main_store','bar','restaurant','curio','kitchen','housekeeping','maintenance','other') NOT NULL DEFAULT 'main_store',
        is_pos_location TINYINT(1) NOT NULL DEFAULT 0,
        allow_room_charge TINYINT(1) NOT NULL DEFAULT 1,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        notes TEXT NULL,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uq_store_location_code (location_code)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $colCheck = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name='store_locations' AND column_name=:col");
    foreach ([
        'allow_room_charge' => "ALTER TABLE store_locations ADD COLUMN allow_room_charge TINYINT(1) NOT NULL DEFAULT 1 AFTER is_pos_location",
        'notes' => "ALTER TABLE store_locations ADD COLUMN notes TEXT NULL AFTER is_active"
    ] as $col => $sql) {
        $colCheck->execute([':col' => $col]);
        if ((int)$colCheck->fetchColumn() === 0) $pdo->exec($sql);
    }

    $pdo->exec("CREATE TABLE IF NOT EXISTS inventory_item_categories (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        category_name VARCHAR(120) NOT NULL,
        category_type ENUM('stock','food','bar','curio','consumable','service','other') NOT NULL DEFAULT 'stock',
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uq_inventory_category_name (category_name)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS inventory_units (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        unit_code VARCHAR(30) NOT NULL,
        unit_name VARCHAR(80) NOT NULL,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        UNIQUE KEY uq_inventory_unit_code (unit_code)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS inventory_items (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        item_code VARCHAR(60) NULL,
        item_name VARCHAR(180) NOT NULL,
        category_id INT UNSIGNED NULL,
        unit_id INT UNSIGNED NULL,
        item_type ENUM('stock','food','bar','curio','consumable','service','recipe','other') NOT NULL DEFAULT 'stock',
        barcode VARCHAR(100) NULL,
        description TEXT NULL,
        cost_price DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        avg_cost DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        selling_price DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        tax_rate DECIMAL(7,4) NOT NULL DEFAULT 0.0000,
        reorder_level DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        reorder_qty DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        is_stock_item TINYINT(1) NOT NULL DEFAULT 1,
        is_pos_item TINYINT(1) NOT NULL DEFAULT 0,
        allow_room_charge TINYINT(1) NOT NULL DEFAULT 1,
        track_expiry TINYINT(1) NOT NULL DEFAULT 0,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uq_inventory_item_code (item_code),
        INDEX idx_inventory_item_name (item_name)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS stock_movements (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        item_id INT UNSIGNED NOT NULL,
        store_id INT UNSIGNED NOT NULL,
        movement_type ENUM('OPENING','GRN','ISSUE','SALE','TRANSFER_IN','TRANSFER_OUT','ADJUSTMENT_IN','ADJUSTMENT_OUT','WASTAGE','RETURN') NOT NULL,
        qty_in DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        qty_out DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        unit_cost DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        total_cost DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        reference_type VARCHAR(60) NULL,
        reference_id BIGINT UNSIGNED NULL,
        notes TEXT NULL,
        movement_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_stock_item_store (item_id, store_id),
        INDEX idx_stock_movement_date (movement_date),
        INDEX idx_stock_reference (reference_type, reference_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS inventory_suppliers (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        supplier_name VARCHAR(180) NOT NULL,
        phone VARCHAR(80) NULL,
        email VARCHAR(150) NULL,
        address TEXT NULL,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uq_inventory_supplier_name (supplier_name)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS inventory_grns (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        grn_no VARCHAR(60) NOT NULL,
        supplier_id INT UNSIGNED NULL,
        supplier_name VARCHAR(180) NULL,
        store_id INT UNSIGNED NOT NULL,
        invoice_no VARCHAR(100) NULL,
        grn_date DATE NOT NULL,
        subtotal DECIMAL(14,2) NOT NULL DEFAULT 0.00,
        tax_amount DECIMAL(14,2) NOT NULL DEFAULT 0.00,
        total_amount DECIMAL(14,2) NOT NULL DEFAULT 0.00,
        status ENUM('posted','void') NOT NULL DEFAULT 'posted',
        notes TEXT NULL,
        created_by INT UNSIGNED NULL,
        void_reason TEXT NULL,
        voided_at DATETIME NULL,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uq_grn_no (grn_no),
        INDEX idx_grn_supplier (supplier_id),
        INDEX idx_grn_store (store_id),
        INDEX idx_grn_date (grn_date)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS taxes (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(255) NOT NULL,
        code VARCHAR(32) NOT NULL,
        type ENUM('Percentage','Fixed','','') NOT NULL,
        rate DECIMAL(10,2) NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS inventory_grn_lines (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        grn_id BIGINT UNSIGNED NOT NULL,
        item_id INT UNSIGNED NOT NULL,
        store_id INT UNSIGNED NULL,
        tax_id INT UNSIGNED NULL,
        tax_name VARCHAR(255) NULL,
        tax_type VARCHAR(30) NULL,
        price_tax_mode ENUM('exclusive','inclusive') NOT NULL DEFAULT 'exclusive',
        unit_price_entered DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        qty DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        unit_cost DECIMAL(14,4) NOT NULL DEFAULT 0.0000,
        tax_rate DECIMAL(7,4) NOT NULL DEFAULT 0.0000,
        line_tax DECIMAL(14,2) NOT NULL DEFAULT 0.00,
        line_total DECIMAL(14,2) NOT NULL DEFAULT 0.00,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_grn_line_grn (grn_id),
        INDEX idx_grn_line_item (item_id),
        INDEX idx_grn_line_tax (tax_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    foreach ([
        'store_id' => "ALTER TABLE inventory_grn_lines ADD COLUMN store_id INT UNSIGNED NULL AFTER item_id",
        'tax_id' => "ALTER TABLE inventory_grn_lines ADD COLUMN tax_id INT UNSIGNED NULL AFTER store_id",
        'tax_name' => "ALTER TABLE inventory_grn_lines ADD COLUMN tax_name VARCHAR(255) NULL AFTER tax_id",
        'tax_type' => "ALTER TABLE inventory_grn_lines ADD COLUMN tax_type VARCHAR(30) NULL AFTER tax_name",
        'price_tax_mode' => "ALTER TABLE inventory_grn_lines ADD COLUMN price_tax_mode ENUM('exclusive','inclusive') NOT NULL DEFAULT 'exclusive' AFTER tax_type",
        'unit_price_entered' => "ALTER TABLE inventory_grn_lines ADD COLUMN unit_price_entered DECIMAL(14,4) NOT NULL DEFAULT 0.0000 AFTER price_tax_mode"
    ] as $col => $sql) {
        if (!grn_column_exists($pdo, 'inventory_grn_lines', $col)) $pdo->exec($sql);
    }

} catch (Throwable $e) {
    $errors[] = 'Could not prepare GRN tables: ' . $e->getMessage();
}

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

        $action = (string)($_POST['action'] ?? '');

        if ($action === 'save_supplier') {
            $supplierName = trim((string)($_POST['supplier_name'] ?? ''));
            $phone = trim((string)($_POST['phone'] ?? ''));
            $email = trim((string)($_POST['email'] ?? ''));
            $address = trim((string)($_POST['address'] ?? ''));

            if ($supplierName === '') throw new RuntimeException('Supplier name is required.');

            $stmt = $pdo->prepare("
                INSERT INTO inventory_suppliers (supplier_name, phone, email, address, is_active)
                VALUES (:supplier_name, :phone, :email, :address, 1)
                ON DUPLICATE KEY UPDATE
                    phone = VALUES(phone),
                    email = VALUES(email),
                    address = VALUES(address),
                    is_active = 1
            ");
            $stmt->execute([
                ':supplier_name' => $supplierName,
                ':phone' => $phone ?: null,
                ':email' => $email ?: null,
                ':address' => $address ?: null,
            ]);

            grn_flash('success', 'Supplier saved successfully.');
            header('Location: grn.php');
            exit;
        }

        if ($action === 'post_grn') {
            $supplierId = (int)($_POST['supplier_id'] ?? 0);
            $supplierNameManual = trim((string)($_POST['supplier_name_manual'] ?? ''));
            $storeId = (int)($_POST['store_id'] ?? 0);
            $invoiceNo = trim((string)($_POST['invoice_no'] ?? ''));
            $grnDate = trim((string)($_POST['grn_date'] ?? date('Y-m-d')));
            $notes = trim((string)($_POST['notes'] ?? ''));

            $itemIds = grn_post_array('item_id');
            $qtys = grn_post_array('qty');
            $unitCosts = grn_post_array('unit_cost');
            $lineAmounts = grn_post_array('line_amount');
            $taxIds = grn_post_array('tax_id');
            $priceTaxModes = grn_post_array('price_tax_mode');
            $lineStoreIds = grn_post_array('line_store_id');

            $maxRows = max(
                count($itemIds),
                count($qtys),
                count($unitCosts),
                count($taxIds),
                count($priceTaxModes),
                count($lineStoreIds)
            );
            if ($maxRows <= 0) {
                throw new RuntimeException('Please add at least one GRN line.');
            }

            $taxMap = [];
            foreach ($pdo->query("SELECT id, name, code, type, rate FROM taxes ORDER BY name ASC, code ASC")->fetchAll(PDO::FETCH_ASSOC) as $taxRow) {
                $taxMap[(int)$taxRow['id']] = $taxRow;
            }

            if ($storeId <= 0) throw new RuntimeException('Please select receiving store.');
            if (strtotime($grnDate) === false) $grnDate = date('Y-m-d');

            /*
             * The header store remains the default. Each GRN line may override it.
             */
            $activeStoreMap = [];
            $activeStoreStmt = $pdo->query("
                SELECT id, location_name
                FROM store_locations
                WHERE is_active = 1
                ORDER BY location_name ASC
            ");
            foreach ($activeStoreStmt->fetchAll(PDO::FETCH_ASSOC) as $storeRow) {
                $activeStoreMap[(int)$storeRow['id']] = (string)$storeRow['location_name'];
            }

            if (!isset($activeStoreMap[$storeId])) {
                throw new RuntimeException('The selected default receiving store is not active.');
            }

            $supplierName = $supplierNameManual;
            if ($supplierId > 0) {
                $stmt = $pdo->prepare("SELECT supplier_name FROM inventory_suppliers WHERE id = :id LIMIT 1");
                $stmt->execute([':id' => $supplierId]);
                $supplierName = (string)($stmt->fetchColumn() ?: $supplierNameManual);
            }

            /*
               Duplicate supplier invoice warning:
               Do not block posting. We warn the user on-screen before submit via JS,
               and we also add a server-side warning after posting in case JS is bypassed.
            */
            $duplicateInvoiceMatch = null;
            if ($invoiceNo !== '' && ($supplierId > 0 || $supplierName !== '')) {
                if ($supplierId > 0) {
                    $stmtDup = $pdo->prepare("
                        SELECT grn_no, grn_date, total_amount, status
                        FROM inventory_grns
                        WHERE supplier_id = :supplier_id
                          AND LOWER(TRIM(invoice_no)) = LOWER(TRIM(:invoice_no))
                        ORDER BY id DESC
                        LIMIT 1
                    ");
                    $stmtDup->execute([
                        ':supplier_id' => $supplierId,
                        ':invoice_no'  => $invoiceNo,
                    ]);
                } else {
                    $stmtDup = $pdo->prepare("
                        SELECT grn_no, grn_date, total_amount, status
                        FROM inventory_grns
                        WHERE supplier_id IS NULL
                          AND LOWER(TRIM(COALESCE(supplier_name,''))) = LOWER(TRIM(:supplier_name))
                          AND LOWER(TRIM(invoice_no)) = LOWER(TRIM(:invoice_no))
                        ORDER BY id DESC
                        LIMIT 1
                    ");
                    $stmtDup->execute([
                        ':supplier_name' => $supplierName,
                        ':invoice_no'    => $invoiceNo,
                    ]);
                }
                $duplicateInvoiceMatch = $stmtDup->fetch(PDO::FETCH_ASSOC) ?: null;
            }

            $lines = [];
            $subtotal = 0.0;
            $taxAmount = 0.0;

            for ($idx = 0; $idx < $maxRows; $idx++) {
                $rowNo = $idx + 1;
                $itemId = grn_int_input($itemIds[$idx] ?? 0);
                $qty = grn_float_input($qtys[$idx] ?? 0);
                $unitPriceInput = grn_float_input($unitCosts[$idx] ?? 0);

                /*
                 * A zero/blank line store means use the GRN header receiving store.
                 */
                $lineStoreId = grn_int_input($lineStoreIds[$idx] ?? 0);
                if ($lineStoreId <= 0) {
                    $lineStoreId = $storeId;
                }

                /* Entered line amount (optional). Empty string means "not
                   used - calculate from unit price as before"; any value
                   (including an explicit 0.00) means the amount is
                   authoritative for this line. */
                $lineAmountRaw = trim(str_replace(',', '', (string)($lineAmounts[$idx] ?? '')));
                $lineAmountInput = ($lineAmountRaw !== '' && is_numeric($lineAmountRaw)) ? (float)$lineAmountRaw : null;

                $taxId = grn_int_input($taxIds[$idx] ?? 0);
                $priceTaxModeRaw = strtolower(trim((string)($priceTaxModes[$idx] ?? 'exclusive')));
                $priceTaxMode = $priceTaxModeRaw === 'inclusive' ? 'inclusive' : 'exclusive';
                $taxName = null;
                $taxType = 'Percentage';
                $taxRate = 0.0;

                // Completely blank cloned rows are ignored. Partially completed rows show a clear row error.
                if ($itemId <= 0 && $qty <= 0 && $unitPriceInput <= 0 && $taxId <= 0 && ($lineAmountInput === null || $lineAmountInput <= 0)) {
                    continue;
                }

                if ($itemId <= 0) {
                    throw new RuntimeException('GRN line ' . $rowNo . ': please select an item.');
                }

                if (!isset($activeStoreMap[$lineStoreId])) {
                    throw new RuntimeException(
                        'GRN line ' . $rowNo . ': please select a valid storage area.'
                    );
                }
                if ($lineAmountInput !== null && $lineAmountInput < 0) {
                    throw new RuntimeException('GRN line ' . $rowNo . ': amount cannot be negative.');
                }
                if ($qty <= 0) {
                    throw new RuntimeException('GRN line ' . $rowNo . ': quantity must be greater than zero.');
                }
                if ($unitPriceInput < 0) {
                    throw new RuntimeException('GRN line ' . $rowNo . ': unit price cannot be negative.');
                }

                if ($taxId > 0) {
                    if (!isset($taxMap[$taxId])) {
                        throw new RuntimeException('GRN line ' . $rowNo . ': invalid tax line selected.');
                    }
                    $taxName = (string)($taxMap[$taxId]['name'] ?? '');
                    $taxType = ((string)($taxMap[$taxId]['type'] ?? 'Percentage')) === 'Fixed' ? 'Fixed' : 'Percentage';
                    $taxRate = grn_float_input($taxMap[$taxId]['rate'] ?? 0);
                }

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

                if (!$item) {
                    throw new RuntimeException('GRN line ' . $rowNo . ': selected inventory item was not found.');
                }
                if ((int)($item['is_stock_item'] ?? 0) !== 1) {
                    throw new RuntimeException('GRN line ' . $rowNo . ': ' . ($item['item_name'] ?? 'selected item') . ' is not marked as a stock item.');
                }

                $amounts = grn_calc_line_amounts($qty, $unitPriceInput, $taxType, $taxRate, $priceTaxMode, $lineAmountInput);
                $lineNet = round((float)$amounts['net'], 2);
                $lineTax = round((float)$amounts['tax'], 2);
                $lineTotal = round((float)$amounts['total'], 2);
                $stockUnitCost = round((float)$amounts['stock_unit_cost'], 4);

                /* When the amount was entered, the true unit price is the
                   derived one (amount / qty) - store that so reprints and
                   audits reflect what actually happened, not a stale 0. */
                $unitPriceStored = $unitPriceInput;
                if ($lineAmountInput !== null && $qty > 0 && $unitPriceInput <= 0) {
                    $unitPriceStored = round($lineAmountInput / $qty, 4);
                }

                $subtotal += $lineNet;
                $taxAmount += $lineTax;

                $lines[] = [
                    'row_no' => $rowNo,
                    'item_id' => $itemId,
                    'item_name' => (string)($item['item_name'] ?? ''),
                    'store_id' => $lineStoreId,
                    'store_name' => $activeStoreMap[$lineStoreId],
                    'qty' => $qty,
                    'unit_price_entered' => $unitPriceStored,
                    'unit_cost' => $stockUnitCost,
                    'tax_id' => $taxId > 0 ? $taxId : null,
                    'tax_name' => $taxName !== '' ? $taxName : null,
                    'tax_type' => $taxType,
                    'price_tax_mode' => $priceTaxMode,
                    'tax_rate' => $taxRate,
                    'line_tax' => $lineTax,
                    'line_total' => $lineTotal,
                ];
            }

            if (!$lines) throw new RuntimeException('Please add at least one GRN line.');

            $totalAmount = round($subtotal + $taxAmount, 2);

            $pdo->beginTransaction();

            $grnNo = grn_no($pdo);
            $stmt = $pdo->prepare("
                INSERT INTO inventory_grns
                (grn_no, supplier_id, supplier_name, store_id, invoice_no, grn_date, subtotal, tax_amount, total_amount, status, notes, created_by)
                VALUES
                (:grn_no, :supplier_id, :supplier_name, :store_id, :invoice_no, :grn_date, :subtotal, :tax_amount, :total_amount, 'posted', :notes, :created_by)
            ");
            $stmt->execute([
                ':grn_no' => $grnNo,
                ':supplier_id' => $supplierId > 0 ? $supplierId : null,
                ':supplier_name' => $supplierName ?: null,
                ':store_id' => $storeId,
                ':invoice_no' => $invoiceNo ?: null,
                ':grn_date' => date('Y-m-d', strtotime($grnDate)),
                ':subtotal' => $subtotal,
                ':tax_amount' => $taxAmount,
                ':total_amount' => $totalAmount,
                ':notes' => $notes ?: null,
                ':created_by' => grn_user_id(),
            ]);
            $grnId = (int)$pdo->lastInsertId();

            $lineStmt = $pdo->prepare("
                INSERT INTO inventory_grn_lines
                (grn_id, item_id, store_id, tax_id, tax_name, tax_type, price_tax_mode, unit_price_entered, qty, unit_cost, tax_rate, line_tax, line_total)
                VALUES
                (:grn_id, :item_id, :store_id, :tax_id, :tax_name, :tax_type, :price_tax_mode, :unit_price_entered, :qty, :unit_cost, :tax_rate, :line_tax, :line_total)
            ");
            $movementStmt = $pdo->prepare("
                INSERT INTO stock_movements
                (item_id, store_id, movement_type, qty_in, qty_out, unit_cost, total_cost, reference_type, reference_id, notes, movement_date)
                VALUES
                (:item_id, :store_id, 'GRN', :qty_in, 0, :unit_cost, :total_cost, 'GRN', :reference_id, :notes, :movement_date)
            ");

            foreach ($lines as $line) {
                try {
                    $lineStmt->execute([
                        ':grn_id' => $grnId,
                        ':item_id' => (int)$line['item_id'],
                        ':store_id' => (int)$line['store_id'],
                        ':tax_id' => $line['tax_id'] !== null ? (int)$line['tax_id'] : null,
                        ':tax_name' => $line['tax_name'] !== null ? (string)$line['tax_name'] : null,
                        ':tax_type' => (string)$line['tax_type'],
                        ':price_tax_mode' => (string)$line['price_tax_mode'],
                        ':unit_price_entered' => (float)$line['unit_price_entered'],
                        ':qty' => (float)$line['qty'],
                        ':unit_cost' => (float)$line['unit_cost'],
                        ':tax_rate' => (float)$line['tax_rate'],
                        ':line_tax' => (float)$line['line_tax'],
                        ':line_total' => (float)$line['line_total'],
                    ]);

                    $movementStmt->execute([
                        ':item_id' => (int)$line['item_id'],
                        ':store_id' => (int)$line['store_id'],
                        ':qty_in' => (float)$line['qty'],
                        ':unit_cost' => (float)$line['unit_cost'],
                        ':total_cost' => round((float)$line['qty'] * (float)$line['unit_cost'], 4),
                        ':reference_id' => $grnId,
                        ':notes' => 'GRN ' . $grnNo
                            . ($invoiceNo ? ' Invoice: ' . $invoiceNo : '')
                            . ' | Received into ' . $line['store_name'],
                        ':movement_date' => date('Y-m-d H:i:s', strtotime($grnDate . ' ' . date('H:i:s'))),
                    ]);

                    grn_recalc_avg_cost($pdo, (int)$line['item_id']);
                } catch (Throwable $lineError) {
                    throw new RuntimeException(
                        'GRN line ' . (int)($line['row_no'] ?? 0) . ' (' . ($line['item_name'] ?: 'item') . ') failed to post: ' . $lineError->getMessage()
                    );
                }
            }

            $pdo->commit();

            if ($duplicateInvoiceMatch) {
                grn_flash(
                    'warning',
                    'Duplicate supplier invoice warning: Invoice ' . $invoiceNo .
                    ' was previously captured for this supplier on GRN ' . ($duplicateInvoiceMatch['grn_no'] ?? '') .
                    ' dated ' . ($duplicateInvoiceMatch['grn_date'] ?? '') .
                    ' for ' . money_fmt($duplicateInvoiceMatch['total_amount'] ?? 0) .
                    '. This new GRN was posted because duplicate invoices are allowed after confirmation.'
                );
            }
            grn_flash('success', 'GRN posted successfully: ' . $grnNo);
            header('Location: grn.php?view_id=' . $grnId);
            exit;
        }

        if ($action === 'void_grn') {
            $grnId = (int)($_POST['grn_id'] ?? 0);
            $reason = trim((string)($_POST['void_reason'] ?? ''));

            if ($grnId <= 0) throw new RuntimeException('Invalid GRN selected.');
            if ($reason === '') throw new RuntimeException('Void reason is required.');

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

            if (!$grn) throw new RuntimeException('GRN not found.');
            if (($grn['status'] ?? '') === 'void') throw new RuntimeException('This GRN is already void.');

            $pdo->beginTransaction();

            $stmt = $pdo->prepare("SELECT * FROM inventory_grn_lines WHERE grn_id = :id");
            $stmt->execute([':id' => $grnId]);
            $lines = $stmt->fetchAll(PDO::FETCH_ASSOC);

            $movementStmt = $pdo->prepare("
                INSERT INTO stock_movements
                (item_id, store_id, movement_type, qty_in, qty_out, unit_cost, total_cost, reference_type, reference_id, notes, movement_date)
                VALUES
                (:item_id, :store_id, 'ISSUE', 0, :qty_out, :unit_cost, :total_cost, 'GRN_VOID', :reference_id, :notes, NOW())
            ");

            foreach ($lines as $line) {
                $movementStmt->execute([
                    ':item_id' => (int)$line['item_id'],
                    ':store_id' => !empty($line['store_id'])
                        ? (int)$line['store_id']
                        : (int)$grn['store_id'],
                    ':qty_out' => (float)$line['qty'],
                    ':unit_cost' => (float)$line['unit_cost'],
                    ':total_cost' => round((float)$line['qty'] * (float)$line['unit_cost'], 4),
                    ':reference_id' => $grnId,
                    ':notes' => 'Void GRN ' . ($grn['grn_no'] ?? '') . ' - ' . $reason,
                ]);

                grn_recalc_avg_cost($pdo, (int)$line['item_id']);
            }

            $stmt = $pdo->prepare("
                UPDATE inventory_grns
                SET status = 'void',
                    void_reason = :reason,
                    voided_at = NOW()
                WHERE id = :id
            ");
            $stmt->execute([':reason' => $reason, ':id' => $grnId]);

            $pdo->commit();

            grn_flash('success', 'GRN voided and reversing stock movements created.');
            header('Location: grn.php?view_id=' . $grnId);
            exit;
        }

    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        grn_flash('danger', $e->getMessage());
        header('Location: grn.php');
        exit;
    }
}

$stores = [];
$items = [];
$suppliers = [];
$taxes = [];

try {
    $stores = $pdo->query("SELECT * FROM store_locations WHERE is_active = 1 ORDER BY location_name ASC")->fetchAll(PDO::FETCH_ASSOC);
    $items = $pdo->query("
        SELECT ii.*, c.category_name, u.unit_code
        FROM inventory_items ii
        LEFT JOIN inventory_item_categories c ON c.id = ii.category_id
        LEFT JOIN inventory_units u ON u.id = ii.unit_id
        WHERE ii.is_active = 1
          AND ii.is_stock_item = 1
        ORDER BY c.category_name ASC, ii.item_name ASC
    ")->fetchAll(PDO::FETCH_ASSOC);
    $suppliers = $pdo->query("SELECT * FROM inventory_suppliers WHERE is_active = 1 ORDER BY supplier_name ASC")->fetchAll(PDO::FETCH_ASSOC);
    $taxes = $pdo->query("SELECT id, name, code, type, rate FROM taxes ORDER BY name ASC, code ASC")->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
    $errors[] = 'Could not load GRN form data: ' . $e->getMessage();
}

$supplierInvoiceHistory = [];
try {
    $stmt = $pdo->query("
        SELECT
            supplier_id,
            supplier_name,
            invoice_no,
            grn_no,
            grn_date,
            total_amount,
            status
        FROM inventory_grns
        WHERE invoice_no IS NOT NULL
          AND TRIM(invoice_no) <> ''
        ORDER BY id DESC
        LIMIT 5000
    "
    );
    foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
        $invoiceKey = strtolower(trim((string)($row['invoice_no'] ?? '')));
        if ($invoiceKey === '') continue;

        $supplierIdKey = (int)($row['supplier_id'] ?? 0);
        $supplierNameKey = strtolower(trim((string)($row['supplier_name'] ?? '')));
        $key = $supplierIdKey > 0 ? ('id:' . $supplierIdKey) : ('manual:' . $supplierNameKey);
        if ($key === 'manual:') continue;

        if (!isset($supplierInvoiceHistory[$key])) {
            $supplierInvoiceHistory[$key] = [];
        }
        if (!isset($supplierInvoiceHistory[$key][$invoiceKey])) {
            $supplierInvoiceHistory[$key][$invoiceKey] = [
                'grn_no'       => (string)($row['grn_no'] ?? ''),
                'grn_date'     => (string)($row['grn_date'] ?? ''),
                'total_amount' => (float)($row['total_amount'] ?? 0),
                'status'       => (string)($row['status'] ?? ''),
            ];
        }
    }
} catch (Throwable $e) {
    $supplierInvoiceHistory = [];
}

$dateFrom = trim((string)($_GET['date_from'] ?? date('Y-m-01')));
$dateTo = trim((string)($_GET['date_to'] ?? date('Y-m-d')));
$storeFilter = (int)($_GET['store_id'] ?? 0);
$supplierFilter = (int)($_GET['supplier_id'] ?? 0);
$statusFilter = trim((string)($_GET['status'] ?? ''));
$search = trim((string)($_GET['q'] ?? ''));

if (strtotime($dateFrom) === false) $dateFrom = date('Y-m-01');
if (strtotime($dateTo) === false) $dateTo = date('Y-m-d');

$where = "g.grn_date BETWEEN :date_from AND :date_to";
$params = [':date_from' => $dateFrom, ':date_to' => $dateTo];

if ($storeFilter > 0) {
    $where .= " AND g.store_id = :store_id";
    $params[':store_id'] = $storeFilter;
}
if ($supplierFilter > 0) {
    $where .= " AND g.supplier_id = :supplier_id";
    $params[':supplier_id'] = $supplierFilter;
}
if ($statusFilter !== '') {
    $where .= " AND g.status = :status";
    $params[':status'] = $statusFilter;
}
if ($search !== '') {
    $where .= " AND (g.grn_no LIKE :q_grn_no OR g.invoice_no LIKE :q_invoice_no OR g.supplier_name LIKE :q_supplier_name OR sl.location_name LIKE :q_store_name)";
    $likeSearch = '%' . $search . '%';
    $params[':q_grn_no'] = $likeSearch;
    $params[':q_invoice_no'] = $likeSearch;
    $params[':q_supplier_name'] = $likeSearch;
    $params[':q_store_name'] = $likeSearch;
}

$grns = [];
$summary = ['count' => 0, 'subtotal' => 0, 'tax' => 0, 'total' => 0, 'void_total' => 0];

try {
    $stmt = $pdo->prepare("
        SELECT
            g.*,
            sl.location_name,
            COUNT(gl.id) AS line_count
        FROM inventory_grns g
        INNER JOIN store_locations sl ON sl.id = g.store_id
        LEFT JOIN inventory_grn_lines gl ON gl.grn_id = g.id
        WHERE $where
        GROUP BY g.id
        ORDER BY g.grn_date DESC, g.id DESC
        LIMIT 300
    ");
    $stmt->execute($params);
    $grns = $stmt->fetchAll(PDO::FETCH_ASSOC);

    foreach ($grns as $g) {
        $summary['count']++;
        if ($g['status'] === 'void') {
            $summary['void_total'] += (float)$g['total_amount'];
        } else {
            $summary['subtotal'] += (float)$g['subtotal'];
            $summary['tax'] += (float)$g['tax_amount'];
            $summary['total'] += (float)$g['total_amount'];
        }
    }
} catch (Throwable $e) {
    $errors[] = 'Could not load GRNs: ' . $e->getMessage();
}

$viewId = (int)($_GET['view_id'] ?? 0);
$viewGrn = null;
$viewLines = [];

if ($viewId > 0) {
    try {
        $stmt = $pdo->prepare("
            SELECT g.*, sl.location_name
            FROM inventory_grns g
            INNER JOIN store_locations sl ON sl.id = g.store_id
            WHERE g.id = :id
            LIMIT 1
        ");
        $stmt->execute([':id' => $viewId]);
        $viewGrn = $stmt->fetch(PDO::FETCH_ASSOC) ?: null;

        if ($viewGrn) {
            $stmt = $pdo->prepare("
                SELECT
                    gl.*,
                    ii.item_name,
                    ii.item_code,
                    u.unit_code,
                    COALESCE(line_store.location_name, header_store.location_name) AS line_store_name
                FROM inventory_grn_lines gl
                INNER JOIN inventory_items ii ON ii.id = gl.item_id
                LEFT JOIN inventory_units u ON u.id = ii.unit_id
                LEFT JOIN store_locations line_store ON line_store.id = gl.store_id
                LEFT JOIN inventory_grns header_grn ON header_grn.id = gl.grn_id
                LEFT JOIN store_locations header_store ON header_store.id = header_grn.store_id
                WHERE gl.grn_id = :id
                ORDER BY gl.id ASC
            ");
            $stmt->execute([':id' => $viewId]);
            $viewLines = $stmt->fetchAll(PDO::FETCH_ASSOC);
        }
    } catch (Throwable $e) {
        $errors[] = 'Could not load GRN detail: ' . $e->getMessage();
    }
}

$flashes = grn_get_flash();

$exportAction = trim((string)($_GET['export'] ?? ''));
if ($exportAction === 'excel') {
    $filename = grn_download_filename('grn_export', 'xls');
    header('Content-Type: application/vnd.ms-excel; charset=utf-8');
    header('Content-Disposition: attachment; filename="' . $filename . '"');
    header('Pragma: no-cache');
    header('Expires: 0');
    echo "Date\tGRN No\tSupplier\tStore\tInvoice\tLines\tStatus\tSubtotal\tTax\tTotal\n";
    foreach ($grns as $g) {
        echo implode("\t", [
            $g['grn_date'],
            $g['grn_no'],
            $g['supplier_name'] ?: '—',
            $g['location_name'] ?: '—',
            $g['invoice_no'] ?: '—',
            (int)$g['line_count'],
            $g['status'],
            number_format((float)$g['subtotal'], 2, '.', ''),
            number_format((float)$g['tax_amount'], 2, '.', ''),
            number_format((float)$g['total_amount'], 2, '.', ''),
        ]) . "\n";
    }
    exit;
}

if ($exportAction === 'excel_detail' && $viewGrn) {
    $filename = grn_download_filename('GRN_' . preg_replace('/[^A-Za-z0-9_-]/', '_', (string)$viewGrn['grn_no']), 'xls');
    header('Content-Type: application/vnd.ms-excel; charset=utf-8');
    header('Content-Disposition: attachment; filename="' . $filename . '"');
    header('Pragma: no-cache');
    header('Expires: 0');
    echo "GRN No\t" . $viewGrn['grn_no'] . "\n";
    echo "Date\t" . $viewGrn['grn_date'] . "\n";
    echo "Supplier\t" . ($viewGrn['supplier_name'] ?: '—') . "\n";
    echo "Store\t" . ($viewGrn['location_name'] ?: '—') . "\n";
    echo "Invoice\t" . ($viewGrn['invoice_no'] ?: '—') . "\n\n";
    echo "Item\tCode\tStorage Area\tQty\tEntered Price\tMode\tTax Line\tTax Type\tTax Rate\tNet\tTax\tTotal\n";
    foreach ($viewLines as $line) {
        $net = (float)$line['line_total'] - (float)$line['line_tax'];
        $entered = !empty($line['unit_price_entered']) ? (float)$line['unit_price_entered'] : (float)$line['unit_cost'];
        echo implode("\t", [
            $line['item_name'] ?? '',
            $line['item_code'] ?? '',
            $line['line_store_name'] ?? ($viewGrn['location_name'] ?? ''),
            number_format((float)$line['qty'], 4, '.', ''),
            number_format($entered, 4, '.', ''),
            $line['price_tax_mode'] ?? 'exclusive',
            $line['tax_name'] ?? 'No Tax',
            $line['tax_type'] ?? '',
            number_format((float)$line['tax_rate'], 2, '.', ''),
            number_format($net, 2, '.', ''),
            number_format((float)$line['line_tax'], 2, '.', ''),
            number_format((float)$line['line_total'], 2, '.', ''),
        ]) . "\n";
    }
    echo "\nSubtotal\t" . number_format((float)$viewGrn['subtotal'], 2, '.', '') . "\n";
    echo "Tax\t" . number_format((float)$viewGrn['tax_amount'], 2, '.', '') . "\n";
    echo "Total\t" . number_format((float)$viewGrn['total_amount'], 2, '.', '') . "\n";
    exit;
}

if ($exportAction === 'pdf' && $viewGrn) {
    $pdfHtml = '<h2>Goods Received Note: ' . h($viewGrn['grn_no']) . '</h2>' .
        '<table cellpadding="4" cellspacing="0" border="0" width="100%">' .
        '<tr><td><b>Date:</b> ' . h($viewGrn['grn_date']) . '</td><td><b>Store:</b> ' . h($viewGrn['location_name']) . '</td></tr>' .
        '<tr><td><b>Supplier:</b> ' . h($viewGrn['supplier_name'] ?: '—') . '</td><td><b>Invoice:</b> ' . h($viewGrn['invoice_no'] ?: '—') . '</td></tr>' .
        '<tr><td><b>Status:</b> ' . h(ucwords($viewGrn['status'])) . '</td><td><b>Total:</b> ' . money_fmt($viewGrn['total_amount']) . '</td></tr>' .
        '</table><br><br>' .
        '<table cellpadding="5" cellspacing="0" border="1" width="100%"><thead><tr style="background-color:#f1f5f9;font-weight:bold;"><th>Item</th><th>Storage Area</th><th align="right">Qty</th><th align="right">Price</th><th>Mode</th><th>Tax</th><th align="right">Net</th><th align="right">Tax</th><th align="right">Total</th></tr></thead><tbody>';
    foreach ($viewLines as $line) {
        $net = (float)$line['line_total'] - (float)$line['line_tax'];
        $priceEntered = isset($line['unit_price_entered']) && (float)$line['unit_price_entered'] > 0 ? (float)$line['unit_price_entered'] : (float)$line['unit_cost'];
        $taxLabel = trim((string)($line['tax_name'] ?? '')) ?: (((float)$line['tax_rate'] > 0) ? money_fmt($line['tax_rate']) . '%' : 'No Tax');
        $pdfHtml .= '<tr>' .
            '<td>' . h($line['item_name']) . '</td>' .
            '<td>' . h($line['line_store_name'] ?? $viewGrn['location_name']) . '</td>' .
            '<td align="right">' . money_fmt($line['qty']) . '</td>' .
            '<td align="right">' . money_fmt($priceEntered) . '</td>' .
            '<td>' . h(ucwords((string)($line['price_tax_mode'] ?? 'exclusive'))) . '</td>' .
            '<td>' . h($taxLabel) . '</td>' .
            '<td align="right">' . money_fmt($net) . '</td>' .
            '<td align="right">' . money_fmt($line['line_tax']) . '</td>' .
            '<td align="right">' . money_fmt($line['line_total']) . '</td>' .
            '</tr>';
    }
    $pdfHtml .= '</tbody><tfoot><tr><td colspan="8" align="right"><b>Subtotal</b></td><td align="right"><b>' . money_fmt($viewGrn['subtotal']) . '</b></td></tr>' .
        '<tr><td colspan="8" align="right"><b>Tax</b></td><td align="right"><b>' . money_fmt($viewGrn['tax_amount']) . '</b></td></tr>' .
        '<tr><td colspan="8" align="right"><b>Total</b></td><td align="right"><b>' . money_fmt($viewGrn['total_amount']) . '</b></td></tr></tfoot></table>';

    $tcpdfLoaded = false;
    foreach ([__DIR__ . '/../../vendor/autoload.php', __DIR__ . '/../../tcpdf/tcpdf.php', __DIR__ . '/../../../tcpdf/tcpdf.php'] as $tcpdfPath) {
        if (file_exists($tcpdfPath)) { require_once $tcpdfPath; $tcpdfLoaded = true; break; }
    }
    if ($tcpdfLoaded && class_exists('TCPDF')) {
        $pdf = new TCPDF('L', PDF_UNIT, 'A4', true, 'UTF-8', false);
        $pdf->SetCreator('GRN');
        $pdf->SetAuthor('GRN');
        $pdf->SetTitle('GRN ' . $viewGrn['grn_no']);
        $pdf->SetMargins(10, 10, 10);
        $pdf->SetAutoPageBreak(true, 12);
        $pdf->AddPage();
        $pdf->SetFont('helvetica', '', 9);
        $pdf->writeHTML($pdfHtml, true, false, true, false, '');
        $pdf->Output('GRN_' . preg_replace('/[^A-Za-z0-9_-]/', '_', $viewGrn['grn_no']) . '.pdf', 'D');
        exit;
    }

    header('Content-Type: text/html; charset=utf-8');
    echo '<!doctype html><html><head><meta charset="utf-8"><title>GRN ' . h($viewGrn['grn_no']) . '</title><style>body{font-family:Arial,sans-serif;padding:20px}table{border-collapse:collapse;width:100%}th,td{font-size:12px;padding:5px}button{margin-bottom:15px}@media print{button{display:none}}</style></head><body><button onclick="window.print()">Print / Save as PDF</button>' . $pdfHtml . '<script>window.print();</script></body></html>';
    exit;
}

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

<div class="hero-band">
    <div class="row g-4 align-items-center">
        <div class="col-lg-8">
            <div class="page-eyebrow text-white-50"><i class="bi bi-truck"></i> Stores &amp; POS Inventory</div>
            <div class="hero-title mt-2">GRN / Purchases</div>
            <div class="hero-subtitle mt-2">
                Receive stock from suppliers, choose a storage area per item, update average costs and post GRN stock movements.
            </div>
        </div>
        <div class="col-lg-4 text-lg-end">
            <a href="stock_movements.php" class="btn btn-light me-2">
                <i class="bi bi-arrow-left-right me-2"></i>Movements
            </a>
            <a href="inventory_items.php" class="btn btn-outline-light">
                <i class="bi bi-box-seam me-2"></i>Items
            </a>
        </div>
    </div>
</div>

<?php foreach ($flashes as $flash): ?>
    <div class="alert alert-<?php echo h($flash['type']); ?>"><?php echo h($flash['message']); ?></div>
<?php endforeach; ?>

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

<div class="row g-4 mb-4">
    <div class="col-md-6 col-xl-3">
        <div class="metric-card">
            <div class="metric-icon bg-primary-subtle text-primary"><i class="bi bi-receipt"></i></div>
            <div><div class="metric-label">GRNs</div><div class="metric-value"><?php echo (int)$summary['count']; ?></div></div>
        </div>
    </div>
    <div class="col-md-6 col-xl-3">
        <div class="metric-card">
            <div class="metric-icon bg-success-subtle text-success"><i class="bi bi-cash-stack"></i></div>
            <div><div class="metric-label">Purchases</div><div class="metric-value small-value"><?php echo money_fmt($summary['total']); ?></div></div>
        </div>
    </div>
    <div class="col-md-6 col-xl-3">
        <div class="metric-card">
            <div class="metric-icon bg-info-subtle text-info"><i class="bi bi-percent"></i></div>
            <div><div class="metric-label">Tax</div><div class="metric-value small-value"><?php echo money_fmt($summary['tax']); ?></div></div>
        </div>
    </div>
    <div class="col-md-6 col-xl-3">
        <div class="metric-card">
            <div class="metric-icon bg-danger-subtle text-danger"><i class="bi bi-x-octagon"></i></div>
            <div><div class="metric-label">Voided</div><div class="metric-value small-value"><?php echo money_fmt($summary['void_total']); ?></div></div>
        </div>
    </div>
</div>

<?php if ($viewGrn): ?>
<div class="card panel-card mb-4 receipt-view" id="grnView">
    <div class="card-header d-flex justify-content-between align-items-center">
        <h5 class="mb-0">GRN <?php echo h($viewGrn['grn_no']); ?></h5>
        <div class="d-flex gap-2 no-print">
            <button type="button" class="btn btn-sm btn-outline-primary" onclick="window.print()"><i class="bi bi-printer me-1"></i>Print</button>
            <a href="grn.php?view_id=<?php echo (int)$viewGrn['id']; ?>&export=pdf" class="btn btn-sm btn-outline-danger"><i class="bi bi-file-earmark-pdf me-1"></i>PDF</a>
            <a href="grn.php?view_id=<?php echo (int)$viewGrn['id']; ?>&export=excel_detail" class="btn btn-sm btn-outline-success"><i class="bi bi-file-earmark-excel me-1"></i>Excel</a>
            <a href="grn.php" class="btn btn-sm btn-outline-secondary">Close</a>
        </div>
    </div>
    <div class="card-body">
        <div class="row g-3 mb-3">
            <div class="col-md-3"><strong>Date:</strong><br><?php echo h($viewGrn['grn_date']); ?></div>
            <div class="col-md-3"><strong>Store:</strong><br><?php echo h($viewGrn['location_name']); ?></div>
            <div class="col-md-3"><strong>Supplier:</strong><br><?php echo h($viewGrn['supplier_name'] ?: '—'); ?></div>
            <div class="col-md-3"><strong>Invoice:</strong><br><?php echo h($viewGrn['invoice_no'] ?: '—'); ?></div>
            <div class="col-md-3"><strong>Status:</strong><br><span class="badge <?php echo $viewGrn['status'] === 'posted' ? 'text-bg-success' : 'text-bg-danger'; ?>"><?php echo h(ucwords($viewGrn['status'])); ?></span></div>
        </div>

        <div class="table-responsive">
            <table class="table table-sm align-middle">
                <thead><tr><th>Item</th><th>Storage Area</th><th class="text-end">Qty</th><th class="text-end">Entered Price</th><th>Price Mode</th><th>Tax Line</th><th class="text-end">Net</th><th class="text-end">Tax</th><th class="text-end">Total</th></tr></thead>
                <tbody>
                <?php foreach ($viewLines as $line): ?>
                    <?php
                        $enteredPrice = !empty($line['unit_price_entered']) ? (float)$line['unit_price_entered'] : (float)$line['unit_cost'];
                        $lineNet = (float)$line['line_total'] - (float)$line['line_tax'];
                        $taxLabel = !empty($line['tax_name']) ? $line['tax_name'] : (((float)$line['tax_rate'] > 0) ? money_fmt($line['tax_rate']) . '%' : 'No Tax');
                    ?>
                    <tr>
                        <td>
                            <strong><?php echo h($line['item_name']); ?></strong>
                            <div class="small text-muted"><?php echo h($line['item_code'] ?: ''); ?> <?php echo h($line['unit_code'] ?: ''); ?></div>
                        </td>
                        <td>
                            <span class="badge text-bg-light border">
                                <?php echo h($line['line_store_name'] ?? $viewGrn['location_name']); ?>
                            </span>
                        </td>
                        <td class="text-end"><?php echo money_fmt($line['qty']); ?></td>
                        <td class="text-end"><?php echo money_fmt($enteredPrice); ?></td>
                        <td><span class="badge text-bg-light border"><?php echo h(ucwords($line['price_tax_mode'] ?? 'exclusive')); ?></span></td>
                        <td><?php echo h($taxLabel); ?><div class="small text-muted"><?php echo h(($line['tax_type'] ?? '') . ((float)$line['tax_rate'] > 0 ? ' ' . money_fmt($line['tax_rate']) : '')); ?></div></td>
                        <td class="text-end"><?php echo money_fmt($lineNet); ?></td>
                        <td class="text-end"><?php echo money_fmt($line['line_tax']); ?></td>
                        <td class="text-end fw-bold"><?php echo money_fmt($line['line_total']); ?></td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
                <tfoot>
                    <tr><th colspan="8" class="text-end">Subtotal</th><th class="text-end"><?php echo money_fmt($viewGrn['subtotal']); ?></th></tr>
                    <tr><th colspan="8" class="text-end">Tax</th><th class="text-end"><?php echo money_fmt($viewGrn['tax_amount']); ?></th></tr>
                    <tr><th colspan="8" class="text-end fs-5">Total</th><th class="text-end fs-5"><?php echo money_fmt($viewGrn['total_amount']); ?></th></tr>
                </tfoot>
            </table>
        </div>

        <?php if (!empty($viewGrn['notes'])): ?>
            <div class="alert alert-info"><?php echo h($viewGrn['notes']); ?></div>
        <?php endif; ?>

        <?php if ($viewGrn['status'] === 'posted'): ?>
            <div class="void-box mt-3">
                <form method="post" onsubmit="return confirm('Void this GRN? This will reverse received stock using ISSUE movements.');">
                    <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                    <input type="hidden" name="action" value="void_grn">
                    <input type="hidden" name="grn_id" value="<?php echo (int)$viewGrn['id']; ?>">
                    <label class="form-label">Void Reason</label>
                    <div class="input-group">
                        <input type="text" name="void_reason" class="form-control" placeholder="Reason required">
                        <button class="btn btn-outline-danger"><i class="bi bi-x-octagon me-1"></i>Void GRN</button>
                    </div>
                </form>
            </div>
        <?php else: ?>
            <div class="alert alert-danger mt-3">
                <strong>Voided:</strong> <?php echo h($viewGrn['void_reason'] ?: 'No reason captured'); ?>
                <?php if (!empty($viewGrn['voided_at'])): ?> on <?php echo h($viewGrn['voided_at']); ?><?php endif; ?>
            </div>
        <?php endif; ?>
    </div>
</div>
<?php endif; ?>

<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3 grn-page-toolbar">
    <div>
        <h5 class="mb-1">Goods Received Notes</h5>
        <div class="small text-muted">Receive supplier stock in a focused full-screen workspace, then review all posted GRNs below.</div>
    </div>
    <div class="d-flex gap-2">
        <button type="button" class="btn btn-outline-primary" id="openSupplierModal"><i class="bi bi-person-plus me-2"></i>Quick Supplier</button>
        <button type="button" class="btn btn-primary" id="openGrnModal"><i class="bi bi-plus-circle me-2"></i>New GRN</button>
    </div>
</div>

<div class="row g-3 grn-workspace">
    <div class="col-xxl-7 col-xl-7 grn-entry-column">
        <div class="card panel-card mb-4" id="grnEntryCard">
            <div class="card-header"><h5 class="mb-0">Post New GRN</h5></div>
            <div class="card-body">
                <form method="post" id="grnForm">
                    <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                    <input type="hidden" name="action" value="post_grn">
                    <input type="hidden" name="duplicate_invoice_confirmed" id="duplicateInvoiceConfirmed" value="0">

                    <div class="mb-3">
                        <label class="form-label">Supplier</label>
                        <select name="supplier_id" id="grnSupplierId" class="form-select search-select supplier-search-select native-search-source" data-placeholder="Type supplier name...">
                            <option value="0">Manual / No supplier selected</option>
                            <?php foreach ($suppliers as $supplier): ?>
                                <option value="<?php echo (int)$supplier['id']; ?>"><?php echo h($supplier['supplier_name']); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>

                    <div class="mb-3">
                        <label class="form-label">Manual Supplier Name</label>
                        <input type="text" name="supplier_name_manual" id="grnSupplierManual" class="form-control" placeholder="Optional if supplier not in list">
                    </div>

                    <div class="row g-3">
                        <div class="col-md-6">
                            <label class="form-label">Receiving Store</label>
                            <select name="store_id" class="form-select" required>
                                <option value="">Select store</option>
                                <?php foreach ($stores as $store): ?>
                                    <option value="<?php echo (int)$store['id']; ?>"><?php echo h($store['location_name']); ?></option>
                                <?php endforeach; ?>
                            </select>
                        </div>
                        <div class="col-md-6">
                            <label class="form-label">GRN Date</label>
                            <input type="date" name="grn_date" class="form-control" value="<?php echo h(date('Y-m-d')); ?>">
                        </div>
                    </div>

                    <div class="mt-3">
                        <label class="form-label">Supplier Invoice No</label>
                        <input type="text" name="invoice_no" id="grnInvoiceNo" class="form-control" placeholder="Invoice / delivery note">
                        <div id="duplicateInvoiceWarning" class="alert alert-warning py-2 px-3 mt-2 mb-0 small d-none"></div>
                    </div>

                    <div class="mt-3">
                        <label class="form-label">Notes</label>
                        <textarea name="notes" class="form-control" rows="2"></textarea>
                    </div>

                    <hr>

                    <div class="d-flex justify-content-between align-items-center mb-2">
                        <h6 class="mb-0 fw-bold">GRN Lines</h6>
                        <button type="button" class="btn btn-sm btn-outline-primary" id="addLine">
                            <i class="bi bi-plus-circle me-1"></i>Add Line
                        </button>
                    </div>

                    <div id="lineBox" class="grn-lines"></div>

                    <div class="grn-total-box mt-3">
                        <div class="d-flex justify-content-between"><span>Subtotal</span><strong id="subtotalLabel">0.00</strong></div>
                        <div class="d-flex justify-content-between"><span>Tax</span><strong id="taxLabel">0.00</strong></div>
                        <div class="d-flex justify-content-between total-row"><span>Total</span><strong id="totalLabel">0.00</strong></div>
                    </div>

                    <button class="btn btn-primary w-100 mt-3">
                        <i class="bi bi-check-circle me-2"></i>Post GRN
                    </button>
                </form>
            </div>
        </div>

        <div class="card panel-card" id="quickSupplierCard">
            <div class="card-header"><h5 class="mb-0">Quick Supplier</h5></div>
            <div class="card-body">
                <form method="post">
                    <?php if (function_exists('csrf_field')) echo csrf_field(); ?>
                    <input type="hidden" name="action" value="save_supplier">

                    <div class="mb-3">
                        <label class="form-label">Supplier Name</label>
                        <input type="text" name="supplier_name" class="form-control" required>
                    </div>
                    <div class="mb-3">
                        <label class="form-label">Phone</label>
                        <input type="text" name="phone" class="form-control">
                    </div>
                    <div class="mb-3">
                        <label class="form-label">Email</label>
                        <input type="email" name="email" class="form-control">
                    </div>
                    <div class="mb-3">
                        <label class="form-label">Address</label>
                        <textarea name="address" class="form-control" rows="2"></textarea>
                    </div>

                    <button class="btn btn-outline-primary w-100">
                        <i class="bi bi-plus-circle me-2"></i>Save Supplier
                    </button>
                </form>
            </div>
        </div>
    </div>

    <div class="col-12 grn-history-column">
        <div class="card panel-card mb-4">
            <div class="card-body">
                <form method="get" class="row g-3 align-items-end">
                    <div class="col-md-2">
                        <label class="form-label">From</label>
                        <input type="date" name="date_from" class="form-control" value="<?php echo h($dateFrom); ?>">
                    </div>
                    <div class="col-md-2">
                        <label class="form-label">To</label>
                        <input type="date" name="date_to" class="form-control" value="<?php echo h($dateTo); ?>">
                    </div>
                    <div class="col-md-2">
                        <label class="form-label">Store</label>
                        <select name="store_id" class="form-select">
                            <option value="0">All stores</option>
                            <?php foreach ($stores as $store): ?>
                                <option value="<?php echo (int)$store['id']; ?>" <?php echo $storeFilter === (int)$store['id'] ? 'selected' : ''; ?>>
                                    <?php echo h($store['location_name']); ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-md-2">
                        <label class="form-label">Supplier</label>
                        <select name="supplier_id" class="form-select search-select supplier-search-select native-search-source" data-placeholder="Search supplier...">
                            <option value="0">All</option>
                            <?php foreach ($suppliers as $supplier): ?>
                                <option value="<?php echo (int)$supplier['id']; ?>" <?php echo $supplierFilter === (int)$supplier['id'] ? 'selected' : ''; ?>>
                                    <?php echo h($supplier['supplier_name']); ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-md-1">
                        <label class="form-label">Status</label>
                        <select name="status" class="form-select">
                            <option value="">All</option>
                            <option value="posted" <?php echo $statusFilter === 'posted' ? 'selected' : ''; ?>>Posted</option>
                            <option value="void" <?php echo $statusFilter === 'void' ? 'selected' : ''; ?>>Void</option>
                        </select>
                    </div>
                    <div class="col-md-2">
                        <label class="form-label">Search</label>
                        <input type="text" name="q" class="form-control" value="<?php echo h($search); ?>">
                    </div>
                    <div class="col-md-1 d-grid">
                        <button class="btn btn-primary" title="Filter"><i class="bi bi-funnel"></i></button>
                    </div>
                    <div class="col-md-1 d-grid">
                        <button type="submit" name="export" value="excel" class="btn btn-success" title="Export Excel"><i class="bi bi-file-earmark-excel"></i></button>
                    </div>
                </form>
            </div>
        </div>

        <div class="card panel-card">
            <div class="card-header d-flex justify-content-between align-items-center">
                <h5 class="mb-0">GRN History</h5>
                <div class="d-flex gap-2 align-items-center">
                    <span class="text-muted small">Showing latest 300 matching GRNs</span>
                    <a class="btn btn-sm btn-outline-success" href="grn.php?<?php echo h(http_build_query(array_merge($_GET, ['export' => 'excel']))); ?>"><i class="bi bi-file-earmark-excel me-1"></i>Excel</a>
                </div>
            </div>
            <div class="card-body p-0">
                <div class="grn-history-scroll">
                    <table class="table table-modern align-middle mb-0 grn-history-table">                        
                        <thead>
                        <tr>
                            <th>Date</th>
                            <th>GRN</th>
                            <th>Supplier</th>
                            <th>Store</th>
                            <th>Invoice</th>
                            <th class="text-end">Lines</th>
                            <th>Status</th>
                            <th class="text-end">Total</th>
                            <th class="text-end">Action</th>
                        </tr>
                        </thead>
                        <tbody>
                        <?php if (!$grns): ?>
                            <tr><td colspan="9" class="text-center text-muted py-4">No GRNs found.</td></tr>
                        <?php else: ?>
                            <?php foreach ($grns as $g): ?>
                                <tr class="<?php echo $g['status'] === 'void' ? 'table-danger' : ''; ?>">
                                    <td><?php echo h($g['grn_date']); ?></td>
                                    <td><strong><?php echo h($g['grn_no']); ?></strong></td>
                                    <td><?php echo h($g['supplier_name'] ?: '—'); ?></td>
                                    <td><?php echo h($g['location_name']); ?></td>
                                    <td><?php echo h($g['invoice_no'] ?: '—'); ?></td>
                                    <td class="text-end"><?php echo (int)$g['line_count']; ?></td>
                                    <td>
                                        <span class="badge <?php echo $g['status'] === 'posted' ? 'text-bg-success' : 'text-bg-danger'; ?>">
                                            <?php echo h(ucwords($g['status'])); ?>
                                        </span>
                                    </td>
                                    <td class="text-end fw-bold"><?php echo money_fmt($g['total_amount']); ?></td>
                                    <td class="text-end">
                                        <a href="grn.php?view_id=<?php echo (int)$g['id']; ?>" class="btn btn-sm btn-outline-primary">
                                            <i class="bi bi-eye"></i>
                                        </a>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                        <?php endif; ?>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>

        <div class="alert alert-info mt-4">
            <strong>Workflow:</strong> Posting a GRN creates <code>GRN</code> rows in <code>stock_movements</code> and updates item average cost.
        </div>
    </div>
</div>

<div class="modal fade grn-workspace-modal" id="grnModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static">
  <div class="modal-dialog modal-dialog-centered grn-workspace-dialog">
    <div class="modal-content grn-workspace-shell">
      <div class="grn-workspace-header">
        <div class="grn-title-group">
          <div class="grn-title-icon"><i class="bi bi-truck"></i></div>
          <div><h4 class="mb-1">Create Goods Received Note</h4><div class="grn-title-sub">Capture supplier invoice details and receive stock into the selected store.</div></div>
        </div>
        <div class="grn-header-actions">
          <span class="grn-posted-pill"><i class="bi bi-box-arrow-in-down"></i> Stock Receipt</span>
          <button type="button" class="grn-icon-btn" data-bs-dismiss="modal"><i class="bi bi-x-lg"></i></button>
        </div>
      </div>
      <div class="grn-workspace-body" id="grnModalMount"></div>
    </div>
  </div>
</div>

<div class="modal fade" id="supplierModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content grn-supplier-shell">
      <div class="modal-header">
        <div><h5 class="modal-title mb-1">Quick Supplier</h5><div class="small text-muted">Add a supplier without leaving the GRN screen.</div></div>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body" id="supplierModalMount"></div>
    </div>
  </div>
</div>

<template id="lineTemplate">
    <div class="grn-line">
        <div class="grn-line-grid align-items-end">
            <div class="grn-item-col">
                <label class="form-label small">Item</label>
                <select name="item_id[]" class="form-select form-select-sm item-select search-select native-search-source" required>
                    <option value="">Select item</option>
                    <?php foreach ($items as $item): ?>
                        <option value="<?php echo (int)$item['id']; ?>"
                                data-cost="<?php echo h($item['avg_cost'] ?: $item['cost_price']); ?>"
                                data-tax="<?php echo h($item['tax_rate']); ?>">
                            <?php echo h($item['item_name']); ?><?php echo $item['item_code'] ? ' - ' . h($item['item_code']) : ''; ?> <?php echo $item['unit_code'] ? '(' . h($item['unit_code']) . ')' : ''; ?>
                        </option>
                    <?php endforeach; ?>
                </select>
            </div>
            <div class="grn-store-col">
                <label class="form-label small">Storage Area</label>
                <select
                    name="line_store_id[]"
                    class="form-select form-select-sm line-store-select"
                    title="Override the default receiving store for this item"
                >
                    <option value="0">Use default receiving store</option>
                    <?php foreach ($stores as $store): ?>
                        <option value="<?php echo (int)$store['id']; ?>">
                            <?php echo h($store['location_name']); ?>
                        </option>
                    <?php endforeach; ?>
                </select>
            </div>
            <div>
                <label class="form-label small">Qty</label>
                <input type="number" step="0.0001" min="0.0001" name="qty[]" class="form-control form-control-sm qty-input text-end" value="1" required>
            </div>
            <div>
                <label class="form-label small">Unit Price</label>
                <input type="number" step="any" min="0" name="unit_cost[]" class="form-control form-control-sm cost-input text-end" value="0">
            </div>
            <div>
                <label class="form-label small">Tax Mode</label>
                <select name="price_tax_mode[]" class="form-select form-select-sm tax-mode-select">
                    <option value="exclusive">Exclusive</option>
                    <option value="inclusive">Inclusive</option>
                </select>
            </div>
            <div>
                <label class="form-label small">Tax Line</label>
                <select name="tax_id[]" class="form-select form-select-sm tax-select">
                    <option value="0" data-rate="0" data-type="Percentage">No Tax</option>
                    <?php foreach ($taxes as $tax): ?>
                        <option value="<?php echo (int)$tax['id']; ?>" data-rate="<?php echo h($tax['rate']); ?>" data-type="<?php echo h($tax['type']); ?>">
                            <?php echo h($tax['name']); ?> (<?php echo h($tax['code']); ?> - <?php echo h($tax['type']); ?> <?php echo h($tax['rate']); ?>)
                        </option>
                    <?php endforeach; ?>
                </select>
            </div>
            <div>
                <label class="form-label small">Amount</label>
                <input type="number" step="any" min="0" name="line_amount[]" class="form-control form-control-sm amount-input text-end" value="" placeholder="qty × price" title="Type the invoice line amount here and the unit price is calculated back for you (amount ÷ qty). Leave it and it fills from qty × unit price.">
            </div>
            <div class="text-end line-tax-box">
                <label class="form-label small">Tax</label>
                <div class="fw-bold line-tax">0.00</div>
            </div>
            <div>
                <button type="button" class="btn btn-sm btn-outline-danger remove-line w-100" title="Remove"><i class="bi bi-x-lg"></i></button>
            </div>
        </div>
        <div class="small text-muted mt-1 line-total">Line total: 0.00</div>
    </div>
</template>


<style>
/* Ultra compact top area */
.hero-band{padding:10px 14px!important;margin-bottom:8px!important}
.hero-title{font-size:1.25rem!important;line-height:1.1!important;margin-top:2px!important}
.hero-subtitle{font-size:.78rem!important;line-height:1.2!important;margin-top:2px!important;opacity:.92}
.page-eyebrow{font-size:.68rem!important;letter-spacing:.06em!important}
.hero-band .btn{padding:5px 9px!important;font-size:.78rem!important;border-radius:8px!important}
.row.g-4.mb-4{margin-bottom:8px!important;--bs-gutter-x:.65rem!important;--bs-gutter-y:.65rem!important}
.metric-card{padding:8px 10px!important;gap:6px!important;min-height:52px!important}
.metric-icon{width:30px!important;height:30px!important;border-radius:8px!important;font-size:12px!important}
.metric-label{font-size:.68rem!important;margin-bottom:1px!important}
.metric-value{font-size:.95rem!important;line-height:1.05!important}
.small-value{font-size:.95rem!important;letter-spacing:0!important}
.panel-card .card-header{padding:9px 12px!important}
.panel-card .card-body{padding:10px!important}
.form-label{font-size:.7rem!important;margin-bottom:2px!important;font-weight:700!important}
.form-control,.form-select{height:31px!important;min-height:31px!important;font-size:.8rem!important;padding:3px 7px!important;border-radius:8px!important}
textarea.form-control{height:auto!important;min-height:46px!important}
.row.g-3{--bs-gutter-x:.5rem!important;--bs-gutter-y:.45rem!important}
.grn-workspace{--bs-gutter-x:.75rem!important;--bs-gutter-y:.75rem!important}
.grn-lines{display:flex;flex-direction:column;gap:8px;max-height:none;overflow:visible;padding-right:4px;position:relative;z-index:5}
.grn-line{border:1px solid rgba(15,23,42,.10);border-radius:14px;padding:10px;background:#f8fafc;position:relative;z-index:1}
.grn-line:has(.item-search-wrap.open){z-index:10050}
.mb-3:has(.item-search-wrap.open),
.col-md-2:has(.item-search-wrap.open){position:relative;z-index:10080}
.grn-line-grid{display:grid;grid-template-columns:minmax(250px,2.1fr) minmax(175px,1.25fr) .65fr .85fr .9fr minmax(190px,1.4fr) .7fr .7fr 42px;gap:7px}
.grn-line .item-select{min-width:270px}
.line-net-box,.line-tax-box{background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:4px 6px;min-height:31px}
.line-net,.line-tax{font-size:.82rem;line-height:1.05}
.grn-total-box{background:#0f172a;color:#fff;border-radius:16px;padding:11px 13px;font-size:.88rem;position:relative;z-index:1}
.grn-total-box .total-row{border-top:1px solid rgba(255,255,255,.18);margin-top:6px;padding-top:6px;font-size:1.05rem}
.void-box{background:#fff7ed;border:1px solid #fed7aa;border-radius:14px;padding:12px}
.receipt-view{border:2px solid rgba(37,99,235,.18)}
.grn-history-scroll{max-height:calc(100vh - 300px);overflow:auto;border-radius:0 0 18px 18px}
.grn-history-table thead th{position:sticky;top:0;z-index:10;background:#f8fafc!important;box-shadow:0 2px 0 rgba(15,23,42,.08)}
.grn-history-table th,.grn-history-table td{padding:7px 8px!important;white-space:nowrap}

/* Native searchable select - no external JS dependency */
.item-search-wrap{position:relative;width:100%;min-width:0;z-index:10060}
.item-search-input{height:31px!important;min-height:31px!important;font-size:.8rem!important;padding:3px 30px 3px 8px!important;border-radius:8px!important;background:#fff!important}
.item-search-wrap::after{content:"\F282";font-family:"bootstrap-icons";position:absolute;right:10px;top:7px;font-size:.72rem;color:#64748b;pointer-events:none}
.item-search-menu{position:absolute;z-index:10070;left:0;right:0;top:calc(100% + 4px);max-height:260px;overflow:auto;background:#fff;border:1px solid #cbd5e1;border-radius:10px;box-shadow:0 14px 30px rgba(15,23,42,.16);padding:5px;display:none}
.item-search-wrap.open .item-search-menu{display:block}
.item-search-option{padding:7px 9px;border-radius:8px;font-size:.82rem;line-height:1.2;cursor:pointer;color:#111827}
.item-search-option:hover,.item-search-option.active{background:#eff6ff;color:#1d4ed8}
.item-search-option .muted{display:block;font-size:.7rem;color:#64748b;margin-top:2px}
.item-search-empty{padding:10px;color:#64748b;font-size:.8rem;text-align:center}
.native-search-source{position:absolute!important;left:-9999px!important;width:1px!important;height:1px!important;opacity:0!important;pointer-events:none!important}

@media (max-width:1400px){.grn-line-grid{grid-template-columns:minmax(260px,2fr) .75fr .85fr .9fr minmax(190px,1.4fr) .75fr .75fr 42px}}
@media (max-width:992px){.grn-line-grid{grid-template-columns:1fr 1fr}.grn-item-col{grid-column:1/-1}}
@media print{
    body *{visibility:hidden!important}
    #grnView,#grnView *{visibility:visible!important}
    #grnView{position:absolute;left:0;top:0;width:100%;box-shadow:none!important;border:0!important}
    .no-print,.void-box,.hero-band,.panel-card:not(#grnView){display:none!important}
}
/* Sleek GRN workspace matching Purchase Orders */
.grn-page-toolbar h5{font-weight:750;color:#101828}.grn-entry-column{display:none!important}.grn-history-column{width:100%!important;max-width:100%!important;flex:0 0 100%!important}.grn-history-scroll{max-height:calc(100vh - 315px)!important;overflow:auto!important}.grn-history-table thead th{position:sticky;top:0;z-index:5;background:#fff!important}
.grn-workspace-modal{--bs-modal-width:none}.grn-workspace-dialog{width:calc(100vw - 34px);max-width:none;height:calc(100vh - 34px);margin:17px auto}.grn-workspace-shell{height:100%;border:0;border-radius:18px;overflow:hidden;background:#f7f9fc;box-shadow:0 30px 90px rgba(2,12,27,.35)}.grn-workspace-header{height:88px;padding:18px 24px;display:flex;align-items:center;justify-content:space-between;background:#fff;border-bottom:1px solid #e7ebf2}.grn-title-group{display:flex;align-items:center;gap:14px}.grn-title-icon{width:44px;height:44px;border:1px solid #cfe0ff;border-radius:12px;display:grid;place-items:center;color:#0d6efd;background:#f4f8ff;font-size:20px}.grn-title-group h4{font-weight:750;color:#101828}.grn-title-sub{font-size:13px;color:#667085}.grn-header-actions{display:flex;gap:10px;align-items:center}.grn-posted-pill{height:40px;padding:0 14px;display:flex;align-items:center;gap:8px;border:1px solid #dfe5ee;border-radius:10px;background:#fff;color:#344054;font-weight:650}.grn-icon-btn{width:40px;height:40px;border:0;background:transparent;border-radius:10px;color:#344054}.grn-icon-btn:hover{background:#f2f4f7}
.grn-workspace-body{padding:16px 20px;min-height:0;flex:1;overflow:hidden}.grn-workspace-body #grnEntryCard{display:flex!important;height:100%;margin:0!important;border:0;background:transparent;box-shadow:none}.grn-workspace-body #grnEntryCard>.card-header{display:none}.grn-workspace-body #grnEntryCard>.card-body{width:100%;padding:0!important;overflow:hidden}.grn-workspace-body #grnForm{height:100%;display:grid;grid-template-columns:minmax(0,1fr) 260px;grid-template-rows:auto minmax(0,1fr) auto;gap:14px}.grn-workspace-body #grnForm>.grn-details-card{grid-column:1/-1}.grn-workspace-body #grnForm>.grn-lines-card{grid-column:1;min-height:0}.grn-workspace-body #grnForm>.grn-summary-card{grid-column:2;grid-row:2;align-self:start}.grn-workspace-body #grnForm>.grn-footer{grid-column:1/-1}
.grn-details-card,.grn-lines-card,.grn-summary-card{background:#fff;border:1px solid #e3e8ef;border-radius:14px;box-shadow:0 2px 5px rgba(16,24,40,.03)}.grn-details-card{padding:18px}.grn-details-grid{display:grid;grid-template-columns:1.4fr 1.1fr .9fr .8fr;gap:16px}.grn-details-grid .form-label,.grn-lines-card .form-label{font-size:12px;font-weight:700;color:#263145;margin-bottom:7px}.grn-details-grid .form-control,.grn-details-grid .form-select{height:44px!important;border-radius:9px!important;border-color:#d8dee8!important;font-size:13px!important}.grn-notes-wrap{margin-top:14px;display:flex;gap:12px;align-items:flex-start;border:1px solid #e1e6ed;border-radius:10px;padding:10px 12px}.grn-notes-icon{width:34px;height:34px;border-radius:8px;background:#f6f8fb;display:grid;place-items:center;color:#344054;margin-top:18px}.grn-notes-wrap textarea{border:0!important;box-shadow:none!important;padding:4px 0!important;min-height:48px!important;resize:none;font-size:13px}
.grn-lines-card{padding:16px;display:flex;flex-direction:column;min-height:0}.grn-lines-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.grn-lines-head h5{font-size:16px;font-weight:750}.grn-lines-card .grn-lines{min-height:0;overflow:auto;border:1px solid #e3e8ef;border-radius:9px;background:#fff;padding:0!important}.grn-lines-card .grn-line{border:0!important;border-bottom:1px solid #edf0f4!important;border-radius:0!important;margin:0!important;padding:10px!important;background:#fff!important;min-width:1040px}.grn-lines-card .grn-line:last-child{border-bottom:0!important}.grn-lines-card .grn-line-grid{display:grid!important;grid-template-columns:minmax(280px,2fr) 80px 115px 120px 150px 120px 90px 42px!important;gap:10px!important}.grn-lines-card .form-control,.grn-lines-card .form-select,.grn-lines-card .item-search-input{height:40px!important;min-height:40px!important;border-radius:8px!important;font-size:12px!important;border-color:#d8dee8!important}.grn-lines-card .remove-line{width:38px!important;height:38px!important;border-radius:8px!important}.grn-lines-card .line-total{font-size:11px;color:#667085;margin-top:5px}
.grn-summary-card{overflow:hidden;background:#fbfcfe}.grn-summary-card h5{padding:16px;margin:0;font-size:16px;font-weight:750}.grn-summary-card .grn-total-box{margin:0!important;border:0!important;border-radius:0!important;background:transparent!important;padding:0!important}.grn-summary-card .grn-total-box>div{padding:9px 16px;font-size:13px}.grn-summary-card .grn-total-box .total-row{margin-top:8px;padding:16px;border-top:1px solid #dfe5ee;color:#0d6efd;font-size:17px;font-weight:750;background:#f3f7ff}.grn-footer{height:72px;padding:13px 16px;display:flex;align-items:center;justify-content:space-between;background:#fff;border:1px solid #e3e8ef;border-radius:14px}.grn-footer .btn{height:44px;border-radius:9px;padding:0 18px;font-size:13px;font-weight:700;display:flex;align-items:center;gap:8px}.grn-footer .grn-post-btn{background:#159455;border-color:#159455;color:#fff;min-width:190px;justify-content:center}.grn-supplier-shell{border:0;border-radius:16px;box-shadow:0 24px 70px rgba(2,12,27,.3)}.grn-supplier-shell #quickSupplierCard{border:0;box-shadow:none;margin:0}.grn-supplier-shell #quickSupplierCard>.card-header{display:none}.grn-supplier-shell #quickSupplierCard>.card-body{padding:0}
@media(max-width:1100px){.grn-workspace-dialog{width:100vw;height:100vh;margin:0}.grn-workspace-shell{border-radius:0}.grn-details-grid{grid-template-columns:1fr 1fr}.grn-workspace-body #grnForm{grid-template-columns:1fr}.grn-workspace-body #grnForm>.grn-details-card,.grn-workspace-body #grnForm>.grn-lines-card,.grn-workspace-body #grnForm>.grn-summary-card,.grn-workspace-body #grnForm>.grn-footer{grid-column:1}.grn-workspace-body #grnForm>.grn-summary-card{grid-row:auto;display:grid;grid-template-columns:1fr 1fr 1fr}.grn-summary-card h5{display:none}}@media(max-width:700px){.grn-workspace-header{padding:14px}.grn-title-sub,.grn-posted-pill{display:none}.grn-workspace-body{padding:10px;overflow:auto}.grn-details-grid{grid-template-columns:1fr}.grn-footer{height:auto;gap:10px;flex-direction:column}.grn-footer .btn{width:100%;justify-content:center}}


/* Item selector: use a Purchase Order-style floating search menu.
   The menu is portalled to <body>, so the scrollable GRN lines panel cannot clip it. */
.item-search-menu.item-search-portal{
    position:fixed !important;
    left:auto;
    right:auto;
    top:auto;
    width:auto;
    min-width:420px;
    max-width:min(720px, calc(100vw - 32px));
    max-height:min(420px, calc(100vh - 40px));
    overflow-y:auto;
    overflow-x:hidden;
    z-index:200000 !important;
    display:none;
    padding:7px;
    border:1px solid #cfd7e3;
    border-radius:11px;
    background:#fff;
    box-shadow:0 22px 55px rgba(15,23,42,.24);
}
.item-search-menu.item-search-portal.portal-open{display:block !important}
.item-search-menu.item-search-portal .item-search-option{
    padding:10px 12px;
    border-radius:8px;
    white-space:normal;
    line-height:1.3;
    cursor:pointer;
}
.item-search-menu.item-search-portal .item-search-option:hover,
.item-search-menu.item-search-portal .item-search-option.active{
    background:#edf4ff;
    color:#123a70;
}
.item-search-menu.item-search-portal .item-search-option .muted{
    display:block;
    margin-top:3px;
    color:#667085;
    font-size:11px;
}
.grn-lines-card,
.grn-lines-card .grn-lines,
.grn-workspace-body,
.grn-workspace-body #grnEntryCard>.card-body{
    isolation:auto;
}


/* Make GRN tax summary explicit and always visible */
.grn-summary-card .grn-total-box > div:nth-child(2){
    background:#fff8e6;
    color:#9a6700;
    border-top:1px solid #f3e3b2;
    border-bottom:1px solid #f3e3b2;
}
.grn-summary-card #taxLabel{
    font-size:15px;
    font-weight:800;
    color:#b26a00;
}
.grn-summary-card #subtotalLabel,
.grn-summary-card #totalLabel{
    font-variant-numeric:tabular-nums;
}


/* Notes card must never contain supplier/item filters */
.grn-notes-wrap .flex-grow-1{width:100%;min-width:0}
.grn-notes-wrap textarea[name="notes"]{display:block!important;width:100%!important}
.grn-notes-wrap select,
.grn-notes-wrap .item-search-wrap,
.grn-notes-wrap .search-select-wrap{display:none!important}


/* Final Notes isolation: only the Notes label and textarea may render here */
.grn-notes-wrap{
    position:relative;
    z-index:1;
    overflow:hidden;
}
.grn-notes-wrap > .flex-grow-1{
    display:block;
    width:100%;
    min-width:0;
}
.grn-notes-wrap textarea[name="notes"]{
    display:block!important;
    width:100%!important;
    max-width:none!important;
}
.grn-notes-wrap input:not([name="notes"]),
.grn-notes-wrap select,
.grn-notes-wrap .item-search-wrap,
.grn-notes-wrap .item-search-input,
.grn-notes-wrap .search-select-wrap{
    display:none!important;
}
.grn-history-column .item-search-wrap{
    position:relative;
    z-index:auto;
}

</style>

<script>
document.addEventListener('DOMContentLoaded', function(){
    const lineBox = document.getElementById('lineBox');
    const template = document.getElementById('lineTemplate');
    const addLineBtn = document.getElementById('addLine');
    const subtotalLabel = document.getElementById('subtotalLabel');
    const taxLabel = document.getElementById('taxLabel');
    const totalLabel = document.getElementById('totalLabel');
    const grnForm = document.getElementById('grnForm');
    const supplierSelect = document.getElementById('grnSupplierId');
    const manualSupplierInput = document.getElementById('grnSupplierManual');
    const invoiceInput = document.getElementById('grnInvoiceNo');
    const duplicateWarning = document.getElementById('duplicateInvoiceWarning');
    const duplicateConfirmed = document.getElementById('duplicateInvoiceConfirmed');
    const supplierInvoiceHistory = <?php echo json_encode($supplierInvoiceHistory, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP); ?>;

    function money(n){ return Number(n || 0).toFixed(2); }
    function normalizeInvoice(value){ return String(value || '').trim().toLowerCase(); }
    function normalizeSupplierName(value){ return String(value || '').trim().toLowerCase(); }
    function selectedSupplierKey(){
        const supplierId = supplierSelect ? parseInt(supplierSelect.value || '0', 10) : 0;
        if (supplierId > 0) return 'id:' + supplierId;
        const manualName = manualSupplierInput ? normalizeSupplierName(manualSupplierInput.value) : '';
        return manualName ? ('manual:' + manualName) : '';
    }
    function findDuplicateInvoice(){
        const key = selectedSupplierKey();
        const invoiceKey = invoiceInput ? normalizeInvoice(invoiceInput.value) : '';
        if (!key || !invoiceKey || !supplierInvoiceHistory[key]) return null;
        return supplierInvoiceHistory[key][invoiceKey] || null;
    }
    function updateDuplicateInvoiceWarning(){
        if (!duplicateWarning) return null;
        const match = findDuplicateInvoice();
        if (!match) {
            duplicateWarning.classList.add('d-none');
            duplicateWarning.innerHTML = '';
            if (duplicateConfirmed) duplicateConfirmed.value = '0';
            return null;
        }
        duplicateWarning.classList.remove('d-none');
        duplicateWarning.innerHTML = '<strong>Warning:</strong> This supplier invoice was already captured on GRN <strong>'
            + (match.grn_no || '') + '</strong> dated <strong>' + (match.grn_date || '') + '</strong>, total <strong>'
            + money(match.total_amount || 0) + '</strong>, status <strong>' + (match.status || '') + '</strong>. You may still proceed if this is intentional.';
        return match;
    }

    function initSearchSelect(context){
        const scope = context || document;
        scope.querySelectorAll('.native-search-source.search-select').forEach(select => {
            if (select.dataset.nativeSearchReady === '1') return;
            select.dataset.nativeSearchReady = '1';

            const wrap = document.createElement('div');
            wrap.className = 'item-search-wrap';

            const input = document.createElement('input');
            input.type = 'text';
            input.className = 'form-control form-control-sm item-search-input';
            input.placeholder = select.getAttribute('data-placeholder')
                || (select.classList.contains('item-select')
                    ? 'Type item name, code or unit...'
                    : 'Type to search...');
            input.autocomplete = 'off';

            /*
             * Append the results menu to document.body rather than inside the
             * scrollable GRN line panel. This is the same practical behaviour
             * as the Purchase Order selector: the list floats above the modal
             * and cannot be cropped by overflow:auto/hidden containers.
             */
            const menu = document.createElement('div');
            menu.className = 'item-search-menu item-search-portal';
            document.body.appendChild(menu);

            select.parentNode.insertBefore(wrap, select);
            wrap.appendChild(input);
            wrap.appendChild(select);

            const getOptions = () => Array.from(select.options).filter(opt => opt.value !== '');

            function optionSubText(opt){
                if (!select.classList.contains('item-select')) return '';
                const cost = opt.getAttribute('data-cost') || '0';
                const tax = opt.getAttribute('data-tax') || '0';
                const parts = [];
                if (Number(cost) > 0) parts.push('Cost: ' + Number(cost).toFixed(4));
                if (Number(tax) > 0) parts.push('Tax: ' + Number(tax).toFixed(2) + '%');
                return parts.join(' · ');
            }

            function positionMenu(){
                const rect = input.getBoundingClientRect();
                const viewportGap = 12;
                const preferredWidth = Math.max(rect.width, 520);
                const width = Math.min(preferredWidth, window.innerWidth - viewportGap * 2);
                let left = rect.left;

                if (left + width > window.innerWidth - viewportGap) {
                    left = window.innerWidth - viewportGap - width;
                }
                left = Math.max(viewportGap, left);

                const availableBelow = window.innerHeight - rect.bottom - viewportGap;
                const availableAbove = rect.top - viewportGap;
                const openAbove = availableBelow < 220 && availableAbove > availableBelow;
                const maxHeight = Math.max(160, Math.min(420, openAbove ? availableAbove - 6 : availableBelow - 6));

                menu.style.width = width + 'px';
                menu.style.left = left + 'px';
                menu.style.maxHeight = maxHeight + 'px';

                if (openAbove) {
                    menu.style.top = 'auto';
                    menu.style.bottom = (window.innerHeight - rect.top + 5) + 'px';
                } else {
                    menu.style.bottom = 'auto';
                    menu.style.top = (rect.bottom + 5) + 'px';
                }
            }

            function closeMenu(){
                wrap.classList.remove('open');
                menu.classList.remove('portal-open');
            }

            function openMenu(){
                positionMenu();
                wrap.classList.add('open');
                menu.classList.add('portal-open');
            }

            function render(term){
                const q = String(term || '').trim().toLowerCase();
                const matches = getOptions()
                    .filter(opt => opt.textContent.toLowerCase().includes(q))
                    .slice(0, 100);

                menu.innerHTML = '';

                if (!matches.length) {
                    const empty = document.createElement('div');
                    empty.className = 'item-search-empty';
                    empty.textContent = 'No matching items found';
                    menu.appendChild(empty);
                    return;
                }

                matches.forEach((opt, idx) => {
                    const item = document.createElement('div');
                    item.className = 'item-search-option' + (idx === 0 ? ' active' : '');
                    item.dataset.value = opt.value;

                    const title = document.createElement('div');
                    title.textContent = opt.textContent.trim();
                    item.appendChild(title);

                    const sub = optionSubText(opt);
                    if (sub) {
                        const muted = document.createElement('span');
                        muted.className = 'muted';
                        muted.textContent = sub;
                        item.appendChild(muted);
                    }

                    item.addEventListener('mousedown', function(e){
                        e.preventDefault();
                        e.stopPropagation();
                        select.value = opt.value;
                        input.value = opt.textContent.trim();
                        closeMenu();
                        select.dispatchEvent(new Event('change', {bubbles:true}));
                    });
                    menu.appendChild(item);
                });
            }

            input.addEventListener('focus', function(){
                render(input.value);
                openMenu();
                input.select();
            });
            input.addEventListener('click', function(){
                render(input.value);
                openMenu();
            });
            input.addEventListener('input', function(){
                select.value = '';
                render(input.value);
                openMenu();
                select.dispatchEvent(new Event('change', {bubbles:true}));
                if (select.classList.contains('item-select')) recalc();
            });
            input.addEventListener('keydown', function(e){
                const options = Array.from(menu.querySelectorAll('.item-search-option'));
                if (e.key === 'Escape') {
                    closeMenu();
                    return;
                }
                if (!options.length) return;

                let activeIndex = options.findIndex(o => o.classList.contains('active'));
                if (activeIndex < 0) activeIndex = 0;

                if (e.key === 'ArrowDown') {
                    e.preventDefault();
                    options[activeIndex]?.classList.remove('active');
                    activeIndex = activeIndex >= options.length - 1 ? 0 : activeIndex + 1;
                    options[activeIndex].classList.add('active');
                    options[activeIndex].scrollIntoView({block:'nearest'});
                } else if (e.key === 'ArrowUp') {
                    e.preventDefault();
                    options[activeIndex]?.classList.remove('active');
                    activeIndex = activeIndex <= 0 ? options.length - 1 : activeIndex - 1;
                    options[activeIndex].classList.add('active');
                    options[activeIndex].scrollIntoView({block:'nearest'});
                } else if (e.key === 'Enter') {
                    e.preventDefault();
                    (options[activeIndex] || options[0])
                        .dispatchEvent(new MouseEvent('mousedown', {bubbles:true}));
                }
            });

            document.addEventListener('mousedown', function(e){
                if (!wrap.contains(e.target) && !menu.contains(e.target)) closeMenu();
            });

            window.addEventListener('resize', function(){
                if (menu.classList.contains('portal-open')) positionMenu();
            });
            document.addEventListener('scroll', function(){
                if (menu.classList.contains('portal-open')) positionMenu();
            }, true);

            const modal = document.getElementById('grnModal');
            if (modal) {
                modal.addEventListener('hidden.bs.modal', closeMenu);
            }

            if (select.value) {
                const selected = select.options[select.selectedIndex];
                input.value = selected ? selected.textContent.trim() : '';
            }
        });
    }

    function calcLine(qty, gross, taxType, taxRate, mode){
        qty = Number(qty || 0);
        const grossInput = Number(gross || 0);
        taxRate = Number(taxRate || 0);
        let net = grossInput;
        let tax = 0;
        let total = grossInput;
        if (taxRate > 0) {
            if (mode === 'inclusive') {
                if (taxType === 'Fixed') {
                    tax = taxRate * Math.max(1, qty);
                    net = Math.max(0, grossInput - tax);
                    total = grossInput;
                } else {
                    tax = grossInput * (taxRate / (100 + taxRate));
                    net = grossInput - tax;
                    total = grossInput;
                }
            } else {
                net = grossInput;
                tax = taxType === 'Fixed' ? taxRate * Math.max(1, qty) : net * (taxRate / 100);
                total = net + tax;
            }
        }
        return {net, tax, total};
    }

    function recalc(){
        let subtotal = 0;
        let taxTotal = 0;
        let grandTotal = 0;

        lineBox.querySelectorAll('.grn-line').forEach(line => {
            const qtyInput = line.querySelector('.qty-input');
            const priceInput = line.querySelector('.cost-input');
            const amountEl = line.querySelector('.amount-input');
            const taxSelect = line.querySelector('.tax-select');
            const modeSelect = line.querySelector('.tax-mode-select');

            const qty = parseFloat(qtyInput ? qtyInput.value : '0') || 0;
            const price = parseFloat(priceInput ? priceInput.value : '0') || 0;
            const amountRaw = amountEl ? String(amountEl.value || '').trim() : '';
            const gross = amountRaw !== '' ? (parseFloat(amountRaw) || 0) : qty * price;

            let taxRate = 0;
            let taxType = 'Percentage';

            if (taxSelect && taxSelect.selectedIndex >= 0) {
                const selectedTax = taxSelect.options[taxSelect.selectedIndex];
                taxRate = parseFloat(
                    selectedTax.dataset.rate
                    || selectedTax.getAttribute('data-rate')
                    || '0'
                ) || 0;
                taxType = (
                    selectedTax.dataset.type
                    || selectedTax.getAttribute('data-type')
                    || 'Percentage'
                ) === 'Fixed' ? 'Fixed' : 'Percentage';
            }

            const mode = modeSelect && modeSelect.value === 'inclusive'
                ? 'inclusive'
                : 'exclusive';

            const amounts = calcLine(qty, gross, taxType, taxRate, mode);

            subtotal += Number(amounts.net || 0);
            taxTotal += Number(amounts.tax || 0);
            grandTotal += Number(amounts.total || 0);

            const taxLineLabel = line.querySelector('.line-tax');
            const totalLabelLine = line.querySelector('.line-total');

            if (taxLineLabel) {
                taxLineLabel.textContent = money(amounts.tax);
                taxLineLabel.title = taxRate > 0
                    ? (taxType === 'Fixed'
                        ? 'Fixed tax ' + money(taxRate)
                        : 'Tax rate ' + money(taxRate) + '%')
                    : 'No tax selected';
            }

            if (totalLabelLine) {
                totalLabelLine.textContent =
                    'Net: ' + money(amounts.net)
                    + ' | Tax: ' + money(amounts.tax)
                    + ' | Line total: ' + money(amounts.total)
                    + (mode === 'inclusive' ? ' (tax inclusive)' : ' (tax exclusive)');
            }
        });

        if (subtotalLabel) subtotalLabel.textContent = money(subtotal);
        if (taxLabel) taxLabel.textContent = money(taxTotal);
        if (totalLabel) totalLabel.textContent = money(grandTotal);
    }

    /* Bidirectional Amount <-> Unit Price sync.
       - Type the AMOUNT (straight off the supplier invoice): unit price is
         back-calculated at high precision (amount / qty, 6dp) - fixes
         invoices whose printed unit price is rounded to 2dp so
         qty x printed-price is a few cents off the true line amount.
       - Type the UNIT PRICE or change QTY: amount fills as qty x price,
         which is exactly how the screen worked before. */
    function syncAmountFromPrice(line){
        const amountEl = line.querySelector('.amount-input');
        if (!amountEl) return;
        const qty = parseFloat(line.querySelector('.qty-input').value || '0') || 0;
        const price = parseFloat(line.querySelector('.cost-input').value || '0') || 0;
        amountEl.value = (qty * price) ? (qty * price).toFixed(2) : '';
    }

    function addLine(){
        const node = template.content.cloneNode(true);
        lineBox.appendChild(node);
        const newLine = lineBox.lastElementChild;
        initSearchSelect(newLine);
        recalc();
    }

    addLineBtn.addEventListener('click', addLine);
    lineBox.addEventListener('change', function(e){
        if (e.target.classList.contains('item-select')) {
            const opt = e.target.options[e.target.selectedIndex];
            const line = e.target.closest('.grn-line');
            const cost = opt ? opt.getAttribute('data-cost') : '0';
            const defaultTax = parseFloat(opt ? (opt.getAttribute('data-tax') || '0') : '0');
            const costInput = line.querySelector('.cost-input');
            const taxSelect = line.querySelector('.tax-select');
            if (costInput && (!costInput.value || parseFloat(costInput.value) <= 0)) costInput.value = cost || '0';
            syncAmountFromPrice(line);
            if (taxSelect) {
                let matched = false;

                if (defaultTax > 0) {
                    Array.from(taxSelect.options).forEach((option, idx) => {
                        const optionRate = parseFloat(option.getAttribute('data-rate') || '0') || 0;
                        if (!matched && Math.abs(optionRate - defaultTax) < 0.01) {
                            taxSelect.selectedIndex = idx;
                            matched = true;
                        }
                    });
                }

                /*
                 * Some inventory items have no default tax_rate even though the
                 * user selects a tax line manually. Always dispatch change so
                 * the summary refreshes after the item selector has finished.
                 */
                taxSelect.dispatchEvent(new Event('change', {bubbles: true}));
            }
        }
        recalc();
    });
    lineBox.addEventListener('change', function(e){
        if (e.target.classList.contains('tax-select')
            || e.target.classList.contains('tax-mode-select')) {
            recalc();
        }
    });

    lineBox.addEventListener('input', function(e){
        const line = e.target.closest('.grn-line');
        if (line) {
            if (e.target.classList.contains('amount-input')) {
                // Amount typed -> derive the unit price at high precision
                const qty = parseFloat(line.querySelector('.qty-input').value || '0') || 0;
                const amount = parseFloat(e.target.value || '0') || 0;
                const costEl = line.querySelector('.cost-input');
                if (costEl && qty > 0) costEl.value = (amount / qty).toFixed(6);
            } else if (e.target.classList.contains('qty-input') || e.target.classList.contains('cost-input')) {
                // Qty or unit price typed -> amount fills as qty x price (original behaviour)
                syncAmountFromPrice(line);
            }
        }
        recalc();
    });
    lineBox.addEventListener('click', function(e){
        const btn = e.target.closest('.remove-line');
        if (btn) {
            btn.closest('.grn-line').remove();
            recalc();
        }
    });
    [supplierSelect, manualSupplierInput, invoiceInput].forEach(el => {
        if (!el) return;
        el.addEventListener('change', updateDuplicateInvoiceWarning);
        el.addEventListener('input', updateDuplicateInvoiceWarning);
    });
    if (supplierSelect) {
        supplierSelect.addEventListener('change', function(){
            if (parseInt(this.value || '0', 10) > 0 && manualSupplierInput) {
                manualSupplierInput.value = '';
            }
            updateDuplicateInvoiceWarning();
        });
    }
    if (grnForm) grnForm.addEventListener('submit', function(e){
        if (!lineBox.querySelector('.grn-line')) {
            e.preventDefault();
            alert('Please add at least one GRN line.');
            return false;
        }

        const match = updateDuplicateInvoiceWarning();
        if (match && duplicateConfirmed && duplicateConfirmed.value !== '1') {
            const ok = confirm(
                'Warning: Supplier invoice ' + (invoiceInput ? invoiceInput.value : '') +
                ' was previously entered for this supplier on GRN ' + (match.grn_no || '') +
                ' dated ' + (match.grn_date || '') +
                ' for ' + money(match.total_amount || 0) + '.\n\nDo you want to proceed and post this GRN anyway?'
            );
            if (!ok) {
                e.preventDefault();
                return false;
            }
            duplicateConfirmed.value = '1';
        }
    });
    /*
     * Wait until the sleek modal layout has moved the original form sections
     * into their final cards. Initialising searchable selects or inserting the
     * first GRN line before that move can leave a generated supplier/filter
     * control inside the Notes card.
     */
    setTimeout(function(){
        /*
         * Initialise searchable controls only inside the GRN entry form.
         * The previous document-wide initialisation also converted the
         * Supplier filter in GRN History (whose selected value is "All").
         * That generated control could visually bleed into the modal Notes
         * area. The history filter remains a normal Bootstrap select.
         */
        initSearchSelect(grnForm);

        /*
         * Defensive cleanup: remove any search widget generated outside the
         * GRN form by an older/cached execution of this script.
         */
        document.querySelectorAll('.item-search-wrap').forEach(function(wrapper){
            if (!grnForm.contains(wrapper)) {
                const nativeSelect = wrapper.querySelector('select');
                if (nativeSelect && wrapper.parentNode) {
                    wrapper.parentNode.insertBefore(nativeSelect, wrapper);
                    nativeSelect.dataset.nativeSearchReady = '';
                    wrapper.remove();
                    nativeSelect.style.display = '';
                }
            }
        });

        if (!lineBox.querySelector('.grn-line')) {
            addLine();
        }
    }, 0);
});
</script>

<script>
document.addEventListener('DOMContentLoaded', function () {
  var entryCard=document.getElementById('grnEntryCard'), supplierCard=document.getElementById('quickSupplierCard');
  var entryMount=document.getElementById('grnModalMount'), supplierMount=document.getElementById('supplierModalMount');
  var form=document.getElementById('grnForm');
  if(entryCard&&entryMount) entryMount.appendChild(entryCard);
  if(supplierCard&&supplierMount) supplierMount.appendChild(supplierCard);

  if(form&&!form.dataset.sleekLayoutReady){
    form.dataset.sleekLayoutReady='1';
    var children=Array.prototype.slice.call(form.children);
    var details=document.createElement('section'); details.className='grn-details-card';
    var grid=document.createElement('div'); grid.className='grn-details-grid';
    var supplierBlock=children.find(function(el){return el.querySelector&&el.querySelector('#grnSupplierId');});
    var manualBlock=children.find(function(el){return el.querySelector&&el.querySelector('#grnSupplierManual');});
    var dateStoreRow=children.find(function(el){return el.classList&&el.classList.contains('row')&&el.querySelector('select[name="store_id"]');});
    var invoiceBlock=children.find(function(el){return el.querySelector&&el.querySelector('#grnInvoiceNo');});
    var notesBlock=children.find(function(el){return el.querySelector&&el.querySelector('textarea[name="notes"]');});
    function addField(block,cls){if(!block)return;block.className='grn-field '+(cls||'');grid.appendChild(block);}
    addField(supplierBlock,'grn-field-supplier'); addField(manualBlock,'');
    if(dateStoreRow){Array.prototype.slice.call(dateStoreRow.children).forEach(function(col){col.className='grn-field';grid.appendChild(col);});dateStoreRow.remove();}
    addField(invoiceBlock,''); details.appendChild(grid);
    if(notesBlock){
      var originalTextarea = notesBlock.querySelector('textarea[name="notes"]');
      var cleanNotes = document.createElement('div');
      cleanNotes.className = 'grn-notes-wrap';

      var icon = document.createElement('div');
      icon.className = 'grn-notes-icon';
      icon.innerHTML = '<i class="bi bi-chat-left-text"></i>';

      var wrap = document.createElement('div');
      wrap.className = 'flex-grow-1';

      var label = document.createElement('label');
      label.className = 'form-label';
      label.setAttribute('for', 'grnNotes');
      label.textContent = 'Notes';

      if (originalTextarea) {
        originalTextarea.id = 'grnNotes';
        originalTextarea.className = 'form-control';
        originalTextarea.rows = 2;
      }

      wrap.appendChild(label);
      if (originalTextarea) wrap.appendChild(originalTextarea);
      cleanNotes.appendChild(icon);
      cleanNotes.appendChild(wrap);

      /*
       * Remove the original wrapper entirely. This guarantees that no
       * supplier/history filter, generated input, or select can survive in
       * the Notes section.
       */
      notesBlock.remove();
      details.appendChild(cleanNotes);
    }
    var hr=form.querySelector('hr');if(hr)hr.remove();
    var linesHeader=Array.prototype.slice.call(form.children).find(function(el){return el.querySelector&&el.querySelector('#addLine');});
    var lineBox=document.getElementById('lineBox');var linesCard=document.createElement('section');linesCard.className='grn-lines-card';
    if(linesHeader){linesHeader.className='grn-lines-head';var heading=linesHeader.querySelector('h6');if(heading)heading.outerHTML='<h5 class="mb-0">GRN Lines</h5>';linesCard.appendChild(linesHeader);}
    if(lineBox)linesCard.appendChild(lineBox);
    var totals=form.querySelector('.grn-total-box');var summary=document.createElement('aside');summary.className='grn-summary-card';summary.innerHTML='<h5>Summary</h5>';if(totals)summary.appendChild(totals);
    var submit=Array.prototype.slice.call(form.querySelectorAll('button')).find(function(btn){return btn.closest('form')===form&&btn.id!=='addLine'&&!btn.classList.contains('remove-line');});
    var footer=document.createElement('div');footer.className='grn-footer';footer.innerHTML='<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><i class="bi bi-x-lg"></i>Cancel</button>';
    if(submit){submit.className='btn grn-post-btn';submit.innerHTML='<i class="bi bi-check-circle"></i>Post GRN';footer.appendChild(submit);}
    var hidden=Array.prototype.slice.call(form.querySelectorAll('input[type="hidden"]'));form.innerHTML='';hidden.forEach(function(el){form.appendChild(el);});form.appendChild(details);form.appendChild(linesCard);form.appendChild(summary);form.appendChild(footer);
  }
  var openGrn=document.getElementById('openGrnModal'),openSupplier=document.getElementById('openSupplierModal');
  if(openGrn&&window.bootstrap)openGrn.addEventListener('click',function(){bootstrap.Modal.getOrCreateInstance(document.getElementById('grnModal')).show();});
  if(openSupplier&&window.bootstrap)openSupplier.addEventListener('click',function(){bootstrap.Modal.getOrCreateInstance(document.getElementById('supplierModal')).show();});
});
</script>

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