<?php
/**
 * Reporte Consolidado de Inventarios Desglosado (Dual DB)
 * 
 * Muestra el resumen global o por sucursal desglosado en:
 * - Con Factura
 * - Sin Factura
 * - Dorados
 * 
 * Permite filtrar por tipo y refleja en tiempo real las ventas y disminuciones del POS.
 */

// 1. Cargar Configuración de Base de Datos y Conexión Dual
$configPath = file_exists(__DIR__ . '/db_config.php') ? __DIR__ . '/db_config.php' : __DIR__ . '/../db_config.php';
$dualPdoPath = file_exists(__DIR__ . '/dual_pdo.php') ? __DIR__ . '/dual_pdo.php' : __DIR__ . '/../dual_pdo.php';

if (!file_exists($configPath) || !file_exists($dualPdoPath)) {
    die(json_encode([
        'success' => false,
        'error' => 'Error de configuración: No se encontraron los archivos db_config.php o dual_pdo.php.'
    ]));
}

$config = require $configPath;
require_once $dualPdoPath;

// Instanciar la conexión dual
$pdo = getDualConnection($config);

// Definir nombres de bases de datos para visualización
$db_principal = $config['db1']['dbname'] ?? 'Principal';
$db_secundaria = $config['db2']['dbname'] ?? 'Secundaria';

// Obtener listado de sucursales para el selector
$sucursales = [];
try {
    $sucursales = $pdo->query("SELECT id, nombre FROM sucursales ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {
    $sucursales = [];
}

// 2. Rutas de API internas
$action = $_GET['action'] ?? '';

// API: Obtener Datos Consolidados Desglosados
if ($action === 'fetch_consolidado') {
    header('Content-Type: application/json; charset=utf-8');
    
    $sucursalId = trim($_GET['sucursal_id'] ?? 'all'); // 'all' o ID entero
    $tipoFilter = trim($_GET['tipo_filter'] ?? 'all'); // 'all', 'facturado', 'no_facturado', 'dorado'
    $q = trim($_GET['q'] ?? '');
    $page = max(1, intval($_GET['page'] ?? 1));
    $limit = max(5, min(100, intval($_GET['limit'] ?? 15)));
    $offset = ($page - 1) * $limit;

    try {
        $where = [];
        $params = [];

        // Filtro por texto de búsqueda
        if (!empty($q)) {
            $where[] = "(p.NombreComercial LIKE ? OR p.NombreGenerico LIKE ? OR p.CodigoInterno LIKE ? OR p.CodigoBarra LIKE ? OR p.laboratorio LIKE ?)";
            $params[] = "%$q%";
            $params[] = "%$q%";
            $params[] = "%$q%";
            $params[] = "%$q%";
            $params[] = "%$q%";
        }

        $whereSql = !empty($where) ? "WHERE " . implode(" AND ", $where) : "";

        // Condiciones por sucursal
        $icmSucCond = ($sucursalId !== 'all') ? "WHERE sucursal_id = " . intval($sucursalId) : "";
        $isuSucCond = ($sucursalId !== 'all') ? "WHERE sucursal_id = " . intval($sucursalId) : "";

        // Filtro HAVING según la clasificación requerida
        $havingCond = "(total_contado > 0 OR stock_total_pos > 0)";
        if ($tipoFilter === 'facturado') {
            $havingCond = "(total_facturado > 0 OR stock_facturado_pos > 0)";
        } elseif ($tipoFilter === 'no_facturado') {
            $havingCond = "(total_no_facturado > 0 OR stock_no_facturado_pos > 0)";
        } elseif ($tipoFilter === 'dorado') {
            $havingCond = "(total_dorado > 0)";
        }

        $sql = "SELECT SQL_CALC_FOUND_ROWS 
                    p.id,
                    p.CodigoInterno,
                    p.CodigoBarra,
                    p.NombreComercial,
                    p.NombreGenerico,
                    p.laboratorio,
                    COALESCE(icm_sub.total_facturado, 0) as total_facturado,
                    COALESCE(icm_sub.total_no_facturado, 0) as total_no_facturado,
                    COALESCE(icm_sub.total_dorado, 0) as total_dorado,
                    COALESCE(icm_sub.total_contado, 0) as total_contado,
                    icm_sub.fecha_conteo_formatted,
                    COALESCE(isu_sub.stock_total_pos, 0) as stock_total_pos,
                    COALESCE(isu_sub.stock_facturado_pos, 0) as stock_facturado_pos,
                    COALESCE(isu_sub.stock_no_facturado_pos, 0) as stock_no_facturado_pos
                FROM productos p
                LEFT JOIN (
                    SELECT 
                        producto_id, 
                        SUM(cantidad) as total_contado,
                        SUM(cantidad_facturado) as total_facturado,
                        SUM(cantidad_no_facturado) as total_no_facturado,
                        SUM(cantidad_dorado) as total_dorado,
                        DATE_FORMAT(MAX(fecha_registro), '%d/%m/%Y %H:%i:%s') as fecha_conteo_formatted
                    FROM inventario_conteos_manuales
                    $icmSucCond
                    GROUP BY producto_id
                ) icm_sub ON p.id = icm_sub.producto_id
                LEFT JOIN (
                    SELECT 
                        producto_id, 
                        SUM(stock_total) as stock_total_pos, 
                        SUM(stock_facturado) as stock_facturado_pos, 
                        SUM(stock_no_facturado) as stock_no_facturado_pos
                    FROM inventario_sucursal
                    $isuSucCond
                    GROUP BY producto_id
                ) isu_sub ON p.id = isu_sub.producto_id
                $whereSql
                GROUP BY p.id
                HAVING $havingCond
                ORDER BY p.id DESC 
                LIMIT $offset, $limit";

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

        // Obtener total de registros que cumplen con el filtro
        $total = $pdo->query("SELECT FOUND_ROWS()")->fetchColumn();
        $totalPages = ceil($total / $limit);

        // KPIs Globales acumulados
        $kpiSql = "SELECT 
                    COUNT(DISTINCT p.id) as total_productos,
                    SUM(COALESCE(isu_sub.stock_total_pos, 0)) as kpi_stock_total,
                    SUM(COALESCE(isu_sub.stock_facturado_pos, 0)) as kpi_stock_facturado,
                    SUM(COALESCE(isu_sub.stock_no_facturado_pos, 0)) as kpi_stock_no_facturado,
                    SUM(COALESCE(icm_sub.total_dorado, 0)) as kpi_stock_dorado
                   FROM productos p
                   LEFT JOIN (
                       SELECT producto_id, 
                              SUM(cantidad) as total_contado,
                              SUM(cantidad_facturado) as total_facturado,
                              SUM(cantidad_no_facturado) as total_no_facturado,
                              SUM(cantidad_dorado) as total_dorado
                       FROM inventario_conteos_manuales
                       $icmSucCond
                       GROUP BY producto_id
                   ) icm_sub ON p.id = icm_sub.producto_id
                   LEFT JOIN (
                       SELECT producto_id, SUM(stock_total) as stock_total_pos, SUM(stock_facturado) as stock_facturado_pos, SUM(stock_no_facturado) as stock_no_facturado_pos
                       FROM inventario_sucursal
                       $isuSucCond
                       GROUP BY producto_id
                   ) isu_sub ON p.id = isu_sub.producto_id
                   $whereSql
                   " . (!empty($whereSql) ? " AND " : " WHERE ") . " $havingCond";
        
        $stmtKpi = $pdo->prepare($kpiSql);
        $stmtKpi->execute($params);
        $kpis = $stmtKpi->fetch(PDO::FETCH_ASSOC);

        echo json_encode([
            'success' => true,
            'data' => $productos,
            'kpis' => $kpis,
            'pagination' => [
                'total' => $total,
                'page' => $page,
                'limit' => $limit,
                'total_pages' => $totalPages
            ]
        ]);
        exit;
    } catch (Exception $e) {
        http_response_code(500);
        echo json_encode(['success' => false, 'error' => $e->getMessage()]);
        exit;
    }
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Consolidado de Inventarios</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
    <style>
        :root {
            --primary: #0b0f19;
            --primary-light: #151f32;
            --accent: #38bdf8;
            --accent-hover: #0284c7;
            --surface: rgba(21, 31, 50, 0.85);
            --border: rgba(255, 255, 255, 0.12);
            --text: #ffffff;
            --text-secondary: #cbd5e1;
        }

        body {
            background-color: var(--primary);
            background-image: 
                radial-gradient(circle at 10% 20%, rgba(56, 189, 248, 0.08) 0%, transparent 40%),
                radial-gradient(circle at 90% 80%, rgba(16, 185, 129, 0.05) 0%, transparent 40%);
            color: var(--text);
            font-family: 'Plus Jakarta Sans', sans-serif;
            min-height: 100vh;
        }

        .glass-card {
            background: var(--surface);
            backdrop-filter: blur(20px);
            border: 1px solid var(--border);
            border-radius: 1.25rem;
            box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
        }

        .kpi-card {
            background: rgba(15, 23, 42, 0.6);
            border: 1px solid var(--border);
            border-radius: 1rem;
            padding: 1.25rem;
            transition: transform 0.2s;
        }

        .kpi-card:hover {
            transform: translateY(-2px);
        }

        .kpi-value {
            font-size: 1.8rem;
            font-weight: 800;
            line-height: 1.2;
        }

        .search-input {
            background: rgba(15, 23, 42, 0.7) !important;
            border: 1px solid var(--border) !important;
            color: white !important;
            border-radius: 0.75rem !important;
            padding: 0.75rem 1.25rem !important;
            font-weight: 500;
        }

        .search-input:focus {
            border-color: var(--accent) !important;
            box-shadow: 0 0 0 4px rgba(56, 189, 248, 0.25) !important;
        }

        .filter-select {
            background: rgba(15, 23, 42, 0.7) !important;
            border: 1px solid var(--border) !important;
            color: white !important;
            border-radius: 0.75rem !important;
            padding: 0.75rem 1.25rem !important;
            font-weight: 600;
            cursor: pointer;
        }

        .table-custom {
            --bs-table-bg: transparent;
            --bs-table-color: var(--text);
            --bs-table-border-color: var(--border);
            margin-bottom: 0;
        }

        .table-custom th {
            font-weight: 700;
            text-transform: uppercase;
            font-size: 0.8rem;
            letter-spacing: 0.75px;
            color: #94a3b8;
            padding: 1.2rem 1rem;
            background: rgba(15, 23, 42, 0.3);
        }

        .table-custom td {
            padding: 0.8rem 1rem;
            vertical-align: middle;
            border-bottom: 1px solid var(--border);
        }

        .tr-product {
            transition: background-color 0.2s;
        }

        .tr-product:hover {
            background-color: rgba(255, 255, 255, 0.02);
        }

        .pagination-custom .page-link {
            background: rgba(21, 31, 50, 0.7);
            border: 1px solid var(--border);
            color: var(--text-secondary);
            padding: 0.6rem 1rem;
            transition: all 0.2s;
        }

        .pagination-custom .page-item.active .page-link {
            background: var(--accent);
            border-color: var(--accent);
            color: white;
            font-weight: 700;
        }

        .color-nombre-comercial {
            color: #ffffff;
            font-weight: 700;
            font-size: 1rem;
        }
        .color-nombre-generico {
            color: #cbd5e1;
            font-size: 0.85rem;
        }
        .color-laboratorio {
            color: #fbbf24;
            font-weight: 700;
            font-size: 0.775rem;
            text-transform: uppercase;
        }
        .color-codigo-interno {
            color: #38bdf8;
            font-weight: 700;
            font-size: 0.875rem;
        }

        .stock-value-box {
            font-weight: 800;
            font-size: 1.1rem;
            border-radius: 0.5rem;
            padding: 0.35rem 0.75rem;
            display: inline-block;
        }

        .badge-type-pill {
            font-weight: 700;
            font-size: 0.75rem;
            padding: 0.25rem 0.5rem;
            border-radius: 0.4rem;
        }
    </style>
</head>
<body class="py-5">

<div class="container">
    <!-- Header -->
    <div class="glass-card p-4 mb-4 text-center">
        <h2 class="fw-bold text-white mb-2"><i class="fa-solid fa-chart-pie text-info me-2"></i> Consolidado General de Inventarios</h2>
        <p class="text-secondary mb-0"><span class="badge bg-info text-dark me-1">Dual DB</span> Mostrando desgloses de existencias físicas y stock en vivo del POS en <code><?= htmlspecialchars($db_principal) ?></code> y <code><?= htmlspecialchars($db_secundaria) ?></code></p>
    </div>

    <!-- TARJETAS DE RESUMEN (KPIs) -->
    <div class="row g-3 mb-4">
        <div class="col-md-3">
            <div class="glass-card kpi-card border-start border-4 border-info">
                <div class="text-secondary small fw-bold uppercase">Productos con Ingreso</div>
                <div class="kpi-value text-info" id="kpiTotalProds">0</div>
            </div>
        </div>
        <div class="col-md-3">
            <div class="glass-card kpi-card border-start border-4 border-success">
                <div class="text-secondary small fw-bold uppercase">Stock Total POS (En Vivo)</div>
                <div class="kpi-value text-success" id="kpiStockTotal">0</div>
            </div>
        </div>
        <div class="col-md-3">
            <div class="glass-card kpi-card border-start border-4 border-primary">
                <div class="text-secondary small fw-bold uppercase">Stock Facturado</div>
                <div class="kpi-value text-primary" id="kpiStockFacturado">0</div>
            </div>
        </div>
        <div class="col-md-3">
            <div class="glass-card kpi-card border-start border-4 border-warning">
                <div class="text-secondary small fw-bold uppercase">Stock No Facturado</div>
                <div class="kpi-value text-warning" id="kpiStockNoFacturado">0</div>
            </div>
        </div>
    </div>

    <!-- Zona de Filtros y Búsqueda -->
    <div class="glass-card p-4 mb-4">
        <div class="row g-3">
            <div class="col-md-4">
                <label class="form-label text-secondary small fw-bold mb-1"><i class="fa-solid fa-store text-info me-1"></i> Sucursal:</label>
                <select id="sucursalFilterSelect" class="form-select filter-select border-info">
                    <option value="all">🌐 TODAS LAS SUCURSALES (Consolidado Global)</option>
                    <?php foreach ($sucursales as $s): ?>
                        <option value="<?= $s['id'] ?>">🏬 <?= htmlspecialchars($s['nombre']) ?></option>
                    <?php endforeach; ?>
                </select>
            </div>
            <div class="col-md-3">
                <label class="form-label text-secondary small fw-bold mb-1"><i class="fa-solid fa-tags text-warning me-1"></i> Filtrar Reporte por:</label>
                <select id="tipoFilterSelect" class="form-select filter-select border-warning">
                    <option value="all">🏷️ Mostrar Todo (Desglosado)</option>
                    <option value="facturado">🟢 Solo Con Factura</option>
                    <option value="no_facturado">🔵 Solo Sin Factura</option>
                    <option value="dorado">⭐ Solo Dorados</option>
                </select>
            </div>
            <div class="col-md-5">
                <label class="form-label text-secondary small fw-bold mb-1"><i class="fa-solid fa-magnifying-glass text-info me-1"></i> Buscar Producto:</label>
                <input type="text" id="searchInput" class="form-control search-input" placeholder="Buscar por nombre, código o laboratorio...">
            </div>
        </div>
    </div>

    <!-- Tabla de Resultados Consolidados -->
    <div class="glass-card p-4 mb-4">
        <div class="table-responsive">
            <table class="table table-custom align-middle">
                <thead>
                    <tr>
                        <th style="width: 15%">Códigos</th>
                        <th style="width: 30%">Producto / Laboratorio</th>
                        <th style="width: 25%" class="text-center">Conteo Físico Desglosado</th>
                        <th style="width: 30%" class="text-center">Stock Activo POS (Actualiza al Vender)</th>
                    </tr>
                </thead>
                <tbody id="consolidadoTableBody">
                    <!-- Dinámico -->
                </tbody>
            </table>
        </div>

        <!-- Paginación -->
        <div class="d-flex justify-content-between align-items-center mt-4 flex-wrap gap-3" id="paginationWrapper">
            <div class="text-secondary small" id="paginationInfo">Mostrando 0 de 0 productos</div>
            <nav>
                <ul class="pagination pagination-custom mb-0" id="paginationList">
                    <!-- Dinámico -->
                </ul>
            </nav>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

<script>
    let currentPage = 1;
    let currentLimit = 15;
    let searchDebounceTimeout = null;

    document.addEventListener('DOMContentLoaded', () => {
        loadConsolidado(1);

        document.getElementById('sucursalFilterSelect').addEventListener('change', () => loadConsolidado(1));
        document.getElementById('tipoFilterSelect').addEventListener('change', () => loadConsolidado(1));

        document.getElementById('searchInput').addEventListener('input', () => {
            clearTimeout(searchDebounceTimeout);
            searchDebounceTimeout = setTimeout(() => {
                loadConsolidado(1);
            }, 300);
        });
    });

    async function loadConsolidado(page = 1) {
        currentPage = page;
        const sucursalId = document.getElementById('sucursalFilterSelect').value;
        const tipoFilter = document.getElementById('tipoFilterSelect').value;
        const q = document.getElementById('searchInput').value.trim();
        const tbody = document.getElementById('consolidadoTableBody');

        tbody.innerHTML = `
            <tr>
                <td colspan="4" class="text-center py-5 text-secondary">
                    <div class="spinner-border text-info mb-3" role="status"></div>
                    <div>Generando reporte desglosado...</div>
                </td>
            </tr>
        `;

        try {
            const res = await fetch(`?action=fetch_consolidado&sucursal_id=${sucursalId}&tipo_filter=${tipoFilter}&q=${encodeURIComponent(q)}&page=${page}&limit=${currentLimit}`);
            const json = await res.json();

            if (!json.success) {
                tbody.innerHTML = `<tr><td colspan="4" class="text-center text-danger py-4"><i class="fa-solid fa-circle-exclamation me-2"></i> Error: ${json.error}</td></tr>`;
                return;
            }

            // Actualizar KPIs
            if (json.kpis) {
                document.getElementById('kpiTotalProds').textContent = json.pagination.total || 0;
                document.getElementById('kpiStockTotal').textContent = json.kpis.kpi_stock_total || 0;
                document.getElementById('kpiStockFacturado').textContent = json.kpis.kpi_stock_facturado || 0;
                document.getElementById('kpiStockNoFacturado').textContent = json.kpis.kpi_stock_no_facturado || 0;
            }

            renderConsolidadoTable(json.data, tipoFilter);
            renderPagination(json.pagination);
        } catch (err) {
            tbody.innerHTML = `<tr><td colspan="4" class="text-center text-danger py-4"><i class="fa-solid fa-circle-exclamation me-2"></i> Error: ${err.message}</td></tr>`;
        }
    }

    function renderConsolidadoTable(products, tipoFilter) {
        const tbody = document.getElementById('consolidadoTableBody');
        tbody.innerHTML = '';

        if (!products || products.length === 0) {
            tbody.innerHTML = '<tr><td colspan="4" class="text-center py-5 text-secondary">No se encontraron productos con inventario o conteo ingresado.</td></tr>';
            return;
        }

        products.forEach(p => {
            const tr = document.createElement('tr');
            tr.className = 'tr-product';

            const cellCodigos = `
                <div class="mb-1"><span class="color-codigo-interno font-monospace">${escapeHtml(p.CodigoInterno || 'S/C')}</span></div>
                <div class="text-secondary font-monospace small" style="opacity: 0.7;">${escapeHtml(p.CodigoBarra || '-')}</div>
            `;

            const cellProducto = `
                <div class="color-nombre-comercial mb-1">${escapeHtml(p.NombreComercial)}</div>
                <div class="color-nombre-generico mb-1" style="opacity: 0.7;">${escapeHtml(p.NombreGenerico || '')}</div>
                <div class="color-laboratorio">${escapeHtml(p.laboratorio || '')}</div>
            `;

            let conteoHtml = '';
            if (tipoFilter === 'facturado') {
                conteoHtml = `<span class="stock-value-box bg-dark text-success border border-success">${p.total_facturado}</span> <span class="badge-type-pill text-success">Facturado</span>`;
            } else if (tipoFilter === 'no_facturado') {
                conteoHtml = `<span class="stock-value-box bg-dark text-info border border-info">${p.total_no_facturado}</span> <span class="badge-type-pill text-info">Sin Factura</span>`;
            } else if (tipoFilter === 'dorado') {
                conteoHtml = `<span class="stock-value-box bg-dark text-warning border border-warning">${p.total_dorado}</span> <span class="badge-type-pill text-warning">Dorado</span>`;
            } else {
                conteoHtml = `
                    <div class="d-flex justify-content-center align-items-center gap-2 mb-1">
                        <span class="badge bg-success" title="Facturado">🟢 ${p.total_facturado}</span>
                        <span class="badge bg-info text-dark" title="Sin Factura">🔵 ${p.total_no_facturado}</span>
                        <span class="badge bg-warning text-dark" title="Dorado">⭐ ${p.total_dorado}</span>
                    </div>
                    <span class="stock-value-box bg-dark text-white border border-secondary">Total: ${p.total_contado}</span>
                `;
            }

            const cellConteo = `
                <div class="text-center">
                    ${conteoHtml}
                    <div class="small text-secondary mt-1" style="font-size: 0.75rem;">
                        ${p.fecha_conteo_formatted ? `<i class="fa-solid fa-clock me-1"></i> ${p.fecha_conteo_formatted}` : 'Sin fecha'}
                    </div>
                </div>
            `;

            const cellPos = `
                <div class="text-center">
                    <span class="stock-value-box bg-dark text-info border border-info mb-1">${p.stock_total_pos}</span>
                    <div class="small text-secondary" style="font-size: 0.75rem;">
                        Facturado: <strong class="text-success">${p.stock_facturado_pos}</strong> | Sin Factura: <strong class="text-warning">${p.stock_no_facturado_pos}</strong>
                    </div>
                </div>
            `;

            tr.innerHTML = `
                <td>${cellCodigos}</td>
                <td>${cellProducto}</td>
                <td>${cellConteo}</td>
                <td>${cellPos}</td>
            `;
            tbody.appendChild(tr);
        });
    }

    function renderPagination(info) {
        const infoSpan = document.getElementById('paginationInfo');
        const list = document.getElementById('paginationList');
        list.innerHTML = '';

        const start = (info.page - 1) * info.limit + 1;
        const end = Math.min(info.total, info.page * info.limit);
        infoSpan.textContent = `Mostrando ${info.total > 0 ? start : 0} a ${end} de ${info.total} productos`;

        if (info.total_pages <= 1) return;

        const prevLi = document.createElement('li');
        prevLi.className = `page-item ${info.page === 1 ? 'disabled' : ''}`;
        prevLi.innerHTML = `<a class="page-link" href="#" onclick="loadConsolidado(${info.page - 1}); return false;"><i class="fa-solid fa-chevron-left"></i></a>`;
        list.appendChild(prevLi);

        let startPage = Math.max(1, info.page - 2);
        let endPage = Math.min(info.total_pages, startPage + 4);
        if (endPage - startPage < 4) {
            startPage = Math.max(1, endPage - 4);
        }

        for (let i = startPage; i <= endPage; i++) {
            const li = document.createElement('li');
            li.className = `page-item ${info.page === i ? 'active' : ''}`;
            li.innerHTML = `<a class="page-link" href="#" onclick="loadConsolidado(${i}); return false;">${i}</a>`;
            list.appendChild(li);
        }

        const nextLi = document.createElement('li');
        nextLi.className = `page-item ${info.page === info.total_pages ? 'disabled' : ''}`;
        nextLi.innerHTML = `<a class="page-link" href="#" onclick="loadConsolidado(${info.page + 1}); return false;"><i class="fa-solid fa-chevron-right"></i></a>`;
        list.appendChild(nextLi);
    }

    function escapeHtml(str) {
        if (!str) return '';
        return str
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;")
            .replace(/'/g, "&#039;");
    }
</script>
</body>
</html>
