<?php
/**
 * AI Monitor - Customer Dashboard
 * Shows AI monitoring subscriptions, issues, on-demand reviews, and notification settings.
 * Accessible to all authenticated users (not just root).
 */

if (!defined('AUTH_USER')) {
    header('Location: login.php');
    exit();
}

$username = AUTH_USER;
$is_root = (AUTH_USER === 'root');

// Load subscriptions server-side for initial state
$subscriptions = [];
$has_subscriptions = false;

try {
    require_once('/docker/whp/web/libs/mysqlmgmt.php');
    $mysql = new mysqlmgmt();
    $db = $mysql->getMySQLConnection();
    if (!$db) {
        throw new Exception('Database connection failed');
    }
    $db->exec("USE whp");

    // sub.* includes the new health cache columns (http/ssl/dns/whois status)
    // added by migration 005_subscription_status_cache.sql
    $query = "
        SELECT sub.*, s.site_name, sub.username AS owner,
               COALESCE(sd.fqdn, d.domain_name) AS primary_domain,
               (SELECT COUNT(*) FROM whp.ai_monitor_issues i WHERE i.subscription_id = sub.id AND i.status = 'open') AS open_issues,
               (SELECT COUNT(*) FROM whp.ai_monitor_issues i WHERE i.subscription_id = sub.id AND i.status = 'open' AND i.severity = 'critical') AS critical_count,
               (SELECT COUNT(*) FROM whp.ai_monitor_issues i WHERE i.subscription_id = sub.id AND i.status = 'open' AND i.severity = 'warning') AS warning_count,
               (SELECT COUNT(*) FROM whp.ai_monitor_issues i WHERE i.subscription_id = sub.id AND i.status = 'open' AND i.severity = 'info') AS info_count
        FROM whp.ai_monitor_subscriptions sub
        JOIN whp.sites s ON sub.site_id = s.id
        JOIN whp.domains d ON s.primary_domain_id = d.id
        LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
        WHERE sub.status = 'active'
    ";

    if ($is_root) {
        // Root sees all subscriptions
        $query .= " ORDER BY sub.username ASC, s.site_name ASC";
        $stmt = $db->query($query);
    } else {
        $query .= " AND sub.username = ? ORDER BY s.site_name ASC";
        $stmt = $db->prepare($query);
        $stmt->execute([$username]);
    }
    $subscriptions = $stmt->fetchAll(PDO::FETCH_ASSOC);
    $has_subscriptions = count($subscriptions) > 0;
} catch (Exception $e) {
    error_log('AI Monitor Customer: Database error: ' . $e->getMessage());
}

$csrf_token = $_SESSION['csrf_token'] ?? '';

/**
 * Render one row of the Site Health strip. The right side shows whatever
 * is most useful for the check:
 *   SSL / Domain — the days-or-date until expiry (e.g. "89d" / "Mar 14, 2026")
 *   HTTP / DNS  — a one-word status (OK / Fail / CDN / Mismatch ...)
 * Color comes from the status; the value uses a darker, larger font so
 * dates stay legible.
 */
function whp_health_indicator(string $label, string $status, string $value): string {
    // Color values are stable between themes. The previous run used
    // *-text-emphasis variables, but the dark-mode "ok" rendered too dark
    // in our theme — the original #198754 reads better in both modes per
    // user feedback. Label colors are theme-switched via the
    // .whp-health-label CSS rules added once at the top of the page.
    $palette = [
        'ok'          => '#198754',
        'expiring'    => '#fd7e14',
        'expired'     => '#dc3545',
        'fail'        => '#dc3545',
        'mismatch'    => '#dc3545',
        'nxdomain'    => '#dc3545',
        'unreachable' => '#dc3545',
        'cdn'         => '#0d6efd',
        'unparsed'    => '#6c757d',
        'skipped'     => '#6c757d',
        'unknown'     => '#6c757d',
    ];
    $color = $palette[$status] ?? '#6c757d';
    if ($value === '') $value = ucfirst($status);

    return '<div class="d-flex align-items-center gap-2 mb-1" style="line-height: 1.3; font-size: 0.85rem;">'
        . '<span style="display:inline-block;width:9px;height:9px;border-radius:50%;background:' . $color . ';flex-shrink:0;" aria-hidden="true"></span>'
        . '<span class="whp-health-label" style="flex-shrink:0;">' . htmlspecialchars($label) . '</span>'
        . '<span class="ms-auto text-truncate" title="' . htmlspecialchars($value) . '" '
        . 'style="color: ' . $color . '; font-weight: 600;">'
        . htmlspecialchars($value)
        . '</span>'
        . '</div>';
}

/**
 * Format a "days remaining" / "Mar 14, 2026" string from an expiry
 * timestamp. Returns the raw status word ("Expired", "Pending", ...) when
 * we have no usable date.
 */
function whp_health_expiry_value(?string $expiresAt, string $status): string {
    static $statusLabels = [
        'unknown'  => 'Pending',
        'skipped'  => 'Skipped',
        'unparsed' => 'Unparsed',
        'expired'  => 'Expired',
    ];
    if (!$expiresAt) {
        return $statusLabels[$status] ?? ucfirst($status);
    }
    $ts = strtotime($expiresAt);
    if ($ts === false) {
        return $statusLabels[$status] ?? ucfirst($status);
    }
    $days = (int) floor(($ts - time()) / 86400);
    if ($days < 0)  return abs($days) . 'd ago';
    if ($days === 0) return 'today';
    if ($days <= 90) return $days . ' days';
    return date('M j, Y', $ts);
}
?>

<style>
    /* Site Health labels (HTTP/SSL/DNS/Domain): pure black in light mode,
       pure white in dark mode. Bootstrap's --bs-body-color was rendering
       too muted in our custom theme overlay, so we set the colors
       explicitly here. */
    .whp-health-label { color: #1a1a1a; font-weight: 500; }
    [data-theme="dark"] .whp-health-label { color: #ffffff; }
    .whp-health-section-title { color: #1a1a1a; opacity: 0.75; }
    [data-theme="dark"] .whp-health-section-title { color: #ffffff; opacity: 0.85; }
</style>
<?php if (!$has_subscriptions): ?>
<!-- No Subscriptions Message -->
<div class="row">
    <div class="col-12">
        <div class="card">
            <div class="card-header">
                <h5 class="card-title mb-0"><i class="fa fa-shield"></i> AI Monitoring</h5>
            </div>
            <div class="card-body">
                <div class="text-center py-5">
                    <i class="fa fa-shield fa-3x text-muted mb-3 d-block"></i>
                    <?php if ($is_root): ?>
                    <h5 class="text-muted">No active AI monitoring subscriptions.</h5>
                    <p class="text-muted">Enable monitoring for sites from the <a href="index.php?page=ai-monitor">AI Monitor admin page</a>.</p>
                    <?php else: ?>
                    <h5 class="text-muted">AI monitoring is not enabled for any of your sites.</h5>
                    <p class="text-muted">Contact your hosting provider to enable this feature.</p>
                    <?php endif; ?>
                </div>
            </div>
        </div>
    </div>
</div>
<?php return; endif; ?>

<!-- Section 1: Subscription Overview -->
<div class="row g-4 mb-4">
    <div class="col-12">
        <div class="card">
            <div class="card-header">
                <h5 class="card-title mb-0"><i class="fa fa-shield"></i> AI Monitoring Overview</h5>
            </div>
            <div class="card-body">
                <div class="row g-3">
                    <?php foreach ($subscriptions as $sub): ?>
                    <div class="col-md-4 col-lg-3">
                        <div class="card h-100 border subscription-card" data-site-id="<?php echo intval($sub['site_id']); ?>" data-subscription-id="<?php echo intval($sub['id']); ?>">
                            <div class="card-body">
                                <div class="d-flex justify-content-between align-items-start mb-2">
                                    <h6 class="card-title mb-0 text-truncate" title="<?php echo htmlspecialchars($sub['primary_domain']); ?>">
                                        <?php echo htmlspecialchars($sub['primary_domain']); ?>
                                    </h6>
                                    <span class="badge bg-<?php echo $sub['status'] === 'active' ? 'success' : 'warning'; ?> ms-2">
                                        <?php echo ucfirst(htmlspecialchars($sub['status'])); ?>
                                    </span>
                                </div>
                                <p class="text-muted small mb-2">
                                    <?php echo htmlspecialchars($sub['site_name']); ?>
                                    <?php if ($is_root && !empty($sub['owner'])): ?>
                                    <span class="badge bg-secondary ms-1" title="Owner"><?php echo htmlspecialchars($sub['owner']); ?></span>
                                    <?php endif; ?>
                                </p>
                                <div class="d-flex gap-2 flex-wrap">
                                    <?php if ($sub['critical_count'] > 0): ?>
                                    <span class="badge bg-danger" title="Critical issues">
                                        <i class="fa fa-exclamation-circle"></i> <?php echo intval($sub['critical_count']); ?>
                                    </span>
                                    <?php endif; ?>
                                    <?php if ($sub['warning_count'] > 0): ?>
                                    <span class="badge bg-warning text-dark" title="Warnings">
                                        <i class="fa fa-exclamation-triangle"></i> <?php echo intval($sub['warning_count']); ?>
                                    </span>
                                    <?php endif; ?>
                                    <?php if ($sub['info_count'] > 0): ?>
                                    <span class="badge bg-info" title="Info">
                                        <i class="fa fa-info-circle"></i> <?php echo intval($sub['info_count']); ?>
                                    </span>
                                    <?php endif; ?>
                                    <?php if ($sub['open_issues'] == 0): ?>
                                    <span class="badge bg-secondary">No open issues</span>
                                    <?php endif; ?>
                                </div>

                                <?php
                                // Site health strip — reads cache columns populated by
                                // scripts/scheduled-site-checks.php (--daily / --initial).
                                $http_state = (string) ($sub['http_status'] ?? 'unknown');
                                $http_value_map = [
                                    'ok'          => 'OK',
                                    'fail'        => 'Failing',
                                    'unreachable' => 'Unreachable',
                                    'unknown'     => 'Pending',
                                ];
                                $http_value = $http_value_map[$http_state] ?? ucfirst($http_state);

                                $ssl_state = (string) ($sub['ssl_status'] ?? 'unknown');
                                $ssl_value = whp_health_expiry_value($sub['ssl_expires_at'] ?? null, $ssl_state);

                                $dns_state = (string) ($sub['dns_status'] ?? 'unknown');
                                $dns_value_map = [
                                    'ok'       => 'OK',
                                    'cdn'      => 'Behind CDN',
                                    'mismatch' => 'Mismatch',
                                    'nxdomain' => 'No DNS',
                                    'skipped'  => 'Skipped',
                                    'unknown'  => 'Pending',
                                ];
                                $dns_value = $dns_value_map[$dns_state] ?? ucfirst($dns_state);

                                $whois_state = (string) ($sub['whois_status'] ?? 'unknown');
                                $whois_value = whp_health_expiry_value($sub['whois_expires_at'] ?? null, $whois_state);
                                ?>
                                <div class="mt-2 pt-2" style="border-top: 1px solid var(--bs-border-color, rgba(128,128,128,0.2));">
                                    <div class="whp-health-section-title text-uppercase fw-semibold mb-2" style="font-size: 0.7rem; letter-spacing: 0.5px;">Site Health</div>
                                    <?php echo whp_health_indicator('HTTP', $http_state, $http_value); ?>
                                    <?php echo whp_health_indicator('SSL', $ssl_state, $ssl_value); ?>
                                    <?php echo whp_health_indicator('DNS', $dns_state, $dns_value); ?>
                                    <?php echo whp_health_indicator('Domain', $whois_state, $whois_value); ?>
                                </div>
                            </div>
                        </div>
                    </div>
                    <?php endforeach; ?>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- Section 2: Issues List -->
<div class="row g-4 mb-4">
    <div class="col-12">
        <div class="card">
            <div class="card-header d-flex justify-content-between align-items-center flex-wrap">
                <h5 class="card-title mb-0"><i class="fa fa-list"></i> Issues</h5>
                <div class="d-flex gap-2 flex-wrap mt-2 mt-md-0">
                    <select id="filter-severity" class="form-select form-select-sm" style="width: auto;">
                        <option value="">All Severities</option>
                        <option value="critical">Critical</option>
                        <option value="warning">Warning</option>
                        <option value="info">Info</option>
                    </select>
                    <select id="filter-site" class="form-select form-select-sm" style="width: auto;">
                        <option value="">All Sites</option>
                        <?php foreach ($subscriptions as $sub): ?>
                        <option value="<?php echo intval($sub['site_id']); ?>"><?php echo htmlspecialchars($sub['primary_domain']); ?></option>
                        <?php endforeach; ?>
                    </select>
                    <select id="filter-status" class="form-select form-select-sm" style="width: auto;">
                        <option value="unread">Unread</option>
                        <option value="all">All</option>
                        <option value="resolved">Resolved</option>
                    </select>
                </div>
            </div>
            <div class="card-body p-0">
                <!-- Bulk action bar (hidden until at least one row is selected) -->
                <div id="issues-bulk-bar" class="px-3 pt-3" style="display: none;">
                    <div class="alert alert-light border d-flex flex-wrap align-items-center mb-2">
                        <span class="me-2"><strong id="issues-selected-count">0</strong> issue(s) selected</span>
                        <button type="button" class="btn btn-sm btn-outline-success ms-2" id="bulk-fix-issues">
                            <i class="fa fa-check-circle"></i> Mark Fixed
                        </button>
                        <button type="button" class="btn btn-sm btn-outline-warning ms-2" id="bulk-ignore-issues">
                            <i class="fa fa-ban"></i> Ignore
                        </button>
                        <button type="button" class="btn btn-sm btn-outline-danger ms-2" id="bulk-delete-issues">
                            <i class="fa fa-trash"></i> Delete
                        </button>
                        <button type="button" class="btn btn-sm btn-outline-secondary ms-2" id="bulk-clear-issues">
                            Clear
                        </button>
                    </div>
                </div>

                <div class="table-responsive">
                    <table class="table table-hover mb-0" id="issues-table">
                        <thead>
                            <tr>
                                <th style="width: 36px;">
                                    <input type="checkbox" id="issues-select-all"
                                           title="Select all visible" aria-label="Select all visible issues">
                                </th>
                                <th style="width: 100px;">Severity</th>
                                <th>Title</th>
                                <th>Site</th>
                                <th>Container</th>
                                <th>First Seen</th>
                                <th>Last Seen</th>
                                <th style="width: 100px;">Status</th>
                            </tr>
                        </thead>
                        <tbody id="issues-tbody">
                            <tr>
                                <td colspan="8" class="text-center py-4">
                                    <i class="fa fa-spinner fa-spin"></i> Loading issues...
                                </td>
                            </tr>
                        </tbody>
                    </table>
                </div>
                <!-- Pagination -->
                <div class="d-flex justify-content-between align-items-center p-3" id="issues-pagination-wrap">
                    <div class="text-muted small" id="issues-count-label"></div>
                    <nav>
                        <ul class="pagination pagination-sm mb-0" id="issues-pagination"></ul>
                    </nav>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- Section 3: On-Demand Log Review -->
<div class="row g-4 mb-4">
    <div class="col-12">
        <div class="card">
            <div class="card-header">
                <h5 class="card-title mb-0"><i class="fa fa-search"></i> On-Demand Log Review</h5>
            </div>
            <div class="card-body">
                <!-- Per-site review request -->
                <div class="row g-3 mb-4">
                    <?php foreach ($subscriptions as $sub): ?>
                    <div class="col-md-4 col-lg-3">
                        <div class="card border h-100">
                            <div class="card-body text-center">
                                <h6 class="text-truncate" title="<?php echo htmlspecialchars($sub['primary_domain']); ?>">
                                    <?php echo htmlspecialchars($sub['primary_domain']); ?>
                                </h6>
                                <div class="mb-2">
                                    <span class="text-muted small review-remaining" data-subscription-id="<?php echo intval($sub['id']); ?>" data-site-id="<?php echo intval($sub['site_id']); ?>" data-daily-limit="<?php echo intval($sub['daily_review_limit']); ?>">
                                        Loading...
                                    </span>
                                </div>
                                <button type="button" class="btn btn-sm btn-primary btn-request-review"
                                        data-site-id="<?php echo intval($sub['site_id']); ?>"
                                        data-subscription-id="<?php echo intval($sub['id']); ?>">
                                    <i class="fa fa-search"></i> Request Log Review
                                </button>
                                <!-- In-progress spinner (hidden by default) -->
                                <div class="review-progress mt-2 d-none" data-site-id="<?php echo intval($sub['site_id']); ?>">
                                    <i class="fa fa-spinner fa-spin text-primary"></i>
                                    <span class="text-muted small">Review in progress...</span>
                                </div>
                            </div>
                        </div>
                    </div>
                    <?php endforeach; ?>
                </div>

                <!-- Per-subscription Traffic panels (free-tier analytics + on-demand AI traffic analysis) -->
                <?php foreach ($subscriptions as $sub):
                    $site_id         = (int) $sub['site_id'];
                    $username        = (string) ($sub['owner'] ?? $sub['username'] ?? AUTH_USER);
                    $primary_domain  = (string) $sub['primary_domain'];
                    $subscription_id = (int) $sub['id'];
                    include __DIR__ . '/../includes/ai-monitor-traffic-panel.php';
                endforeach; ?>

                <!-- Review History -->
                <h6 class="mb-3"><i class="fa fa-history"></i> Review History</h6>
                <div class="row mb-3">
                    <div class="col-auto">
                        <select id="review-history-site" class="form-select form-select-sm">
                            <option value="">All Sites</option>
                            <?php foreach ($subscriptions as $sub): ?>
                            <option value="<?php echo intval($sub['site_id']); ?>"><?php echo htmlspecialchars($sub['primary_domain']); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                </div>
                <div class="table-responsive">
                    <table class="table table-hover mb-0" id="review-history-table">
                        <thead>
                            <tr>
                                <th>Date</th>
                                <th>Site</th>
                                <th>Type</th>
                                <th>Status</th>
                                <th>Summary</th>
                                <th style="width: 80px;"></th>
                            </tr>
                        </thead>
                        <tbody id="review-history-tbody">
                            <tr>
                                <td colspan="6" class="text-center py-4">
                                    <i class="fa fa-spinner fa-spin"></i> Loading review history...
                                </td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- Section 4: Notification Settings -->
<div class="row g-4 mb-4">
    <div class="col-12">
        <div class="card">
            <div class="card-header">
                <h5 class="card-title mb-0"><i class="fa fa-bell"></i> Notification Settings</h5>
            </div>
            <div class="card-body">
                <p class="text-muted mb-3">Set an email address per site to receive issue notifications. Leave blank to disable email alerts.</p>
                <?php foreach ($subscriptions as $sub): ?>
                <div class="row align-items-center mb-3 notification-row" data-subscription-id="<?php echo intval($sub['id']); ?>">
                    <div class="col-md-3">
                        <strong><?php echo htmlspecialchars($sub['primary_domain']); ?></strong>
                    </div>
                    <div class="col-md-5">
                        <div class="input-group input-group-sm">
                            <input type="email" class="form-control notification-email"
                                   value="<?php echo htmlspecialchars($sub['notification_email'] ?? ''); ?>"
                                   placeholder="email@example.com"
                                   data-subscription-id="<?php echo intval($sub['id']); ?>">
                            <button type="button" class="btn btn-outline-primary btn-save-notification"
                                    data-subscription-id="<?php echo intval($sub['id']); ?>">
                                <i class="fa fa-save"></i> Save
                            </button>
                        </div>
                    </div>
                    <div class="col-md-4">
                        <span class="notification-save-status text-muted small" data-subscription-id="<?php echo intval($sub['id']); ?>"></span>
                    </div>
                </div>
                <?php endforeach; ?>
            </div>
        </div>
    </div>
</div>

<!-- Suppressed Alerts Panel -->
<div class="card mt-3">
    <div class="card-header d-flex justify-content-between align-items-center"
         role="button" data-bs-toggle="collapse" data-bs-target="#suppressionsBody">
        <strong><i class="fa fa-ban"></i> Suppressed Alerts</strong>
        <i class="fa fa-caret-down"></i>
    </div>
    <div id="suppressionsBody" class="collapse">
        <div class="card-body">
            <table class="table table-sm">
                <thead>
                    <tr>
                        <th>Site</th>
                        <th>Issue</th>
                        <th>Reason</th>
                        <th>Suppressed at</th>
                        <th>Count</th>
                        <th></th>
                    </tr>
                </thead>
                <tbody id="suppressionsTableBody">
                    <tr><td colspan="6" class="text-muted">Click to load…</td></tr>
                </tbody>
            </table>
        </div>
    </div>
</div>

<!-- Issue Detail Modal -->
<div class="modal fade" id="issueDetailModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-lg">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="issueDetailTitle">Issue Details</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                <div class="mb-3">
                    <span class="badge" id="issueDetailSeverity"></span>
                    <span class="badge bg-secondary" id="issueDetailCategory"></span>
                    <span class="text-muted small ms-2" id="issueDetailMeta"></span>
                </div>
                <h6>Diagnosis</h6>
                <p id="issueDetailDiagnosis"></p>
                <h6>Recommendation</h6>
                <p id="issueDetailRecommendation"></p>
                <div id="issueDetailAutoFixWrap" class="d-none mt-3">
                    <h6><i class="fa fa-magic"></i> Suggested Fix</h6>
                    <div class="card border-info">
                        <div class="card-body py-2 px-3">
                            <div id="issueDetailAutoFixDescription" class="small mb-2"></div>
                            <pre class="bg-dark text-light p-2 rounded mb-2" style="font-size: 0.85em; white-space: pre-wrap;"><code id="issueDetailAutoFixCommand"></code></pre>
                            <div id="issueDetailAutoFixActions" class="d-flex align-items-center gap-2 flex-wrap">
                                <button type="button" class="btn btn-sm btn-info btn-apply-fix-modal" data-issue-id="">
                                    <i class="fa fa-play"></i> Apply this fix
                                </button>
                                <span id="issueDetailAutoFixStatus" class="small text-muted"></span>
                            </div>
                            <div id="issueDetailAutoFixOutputWrap" class="mt-2 d-none">
                                <details>
                                    <summary class="small text-muted" style="cursor: pointer;">Command output</summary>
                                    <pre class="bg-dark text-light p-2 rounded mt-1" style="max-height: 200px; overflow-y: auto; font-size: 0.8em; white-space: pre-wrap;" id="issueDetailAutoFixOutput"></pre>
                                </details>
                            </div>
                        </div>
                    </div>
                </div>
                <div id="issueDetailEvidenceWrap" class="d-none mt-3">
                    <h6><i class="fa fa-quote-left"></i> Evidence (verbatim log line)</h6>
                    <pre class="bg-dark text-light p-3 rounded" style="max-height: 200px; overflow-y: auto; font-size: 0.85em; white-space: pre-wrap;" id="issueDetailEvidence"></pre>
                    <div id="issueDetailEvidenceMeta" class="small text-muted mt-1"></div>
                    <div id="issueDetailStaleWarning" class="alert alert-warning small mt-2 d-none">
                        <i class="fa fa-clock-o"></i>
                        <strong>This issue may no longer be relevant.</strong>
                        <span id="issueDetailStaleReason"></span>
                    </div>
                </div>
                <div id="issueDetailLogWrap" class="d-none mt-3">
                    <h6>Surrounding Log Context</h6>
                    <pre class="bg-dark text-light p-3 rounded" style="max-height: 300px; overflow-y: auto; font-size: 0.85em;" id="issueDetailLog"></pre>
                </div>
                <div id="issueDetailExplainWrap" class="d-none mt-3">
                    <h6><i class="fa fa-comments-o"></i> Explanation</h6>
                    <div id="issueDetailExplain" class="p-3 rounded border" style="white-space: pre-wrap; line-height: 1.5; background: var(--bs-tertiary-bg, rgba(128,128,128,0.08)); color: var(--bs-body-color);"></div>
                    <small class="text-muted d-block mt-1" id="issueDetailExplainMeta"></small>
                </div>
            </div>
            <div class="modal-footer justify-content-between">
                <button type="button" class="btn btn-sm btn-outline-info btn-explain-modal" data-issue-id="">
                    <i class="fa fa-comments-o"></i> Explain this to me
                </button>
                <div>
                    <button type="button" class="btn btn-sm btn-outline-success btn-mark-fixed-modal" data-issue-id="">
                        <i class="fa fa-check-circle"></i> Mark Fixed
                    </button>
                    <button type="button" class="btn btn-sm btn-outline-warning btn-mark-ignored-modal" data-issue-id="">
                        <i class="fa fa-ban"></i> Ignore
                    </button>
                    <button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- Review Detail Modal -->
<div class="modal fade" id="reviewDetailModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-lg">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Log Review Details</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body" id="reviewDetailBody">
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
            </div>
        </div>
    </div>
</div>

<!-- Modern Chart.js for the per-subscription Traffic sparklines.
     The legacy Chart.js 1.x bundled in index.php is incompatible with the
     `new Chart(ctx, config)` API the traffic panel uses, so we load v4
     scoped to this page only. -->
<script src="js/chartjs/chart-v4.umd.min.js"></script>

<script>
(function() {
    'use strict';

    var csrfToken = <?php echo json_encode($csrf_token); ?>;
    var isRoot = <?php echo $is_root ? 'true' : 'false'; ?>;
    var apiBase = 'api/ai-monitor-customer.php';
    var currentIssuesPage = 1;
    var perPage = 20;
    var pollTimers = {};
    var selectedIssues = new Set();

    // ========================
    // Utility Functions
    // ========================

    function byId(id) { return document.getElementById(id); }
    function qa(sel) { return document.querySelectorAll(sel); }
    function q(sel) { return document.querySelector(sel); }

    function apiGet(action, params) {
        params = params || {};
        params.action = action;
        var qs = Object.keys(params).map(function(k) { return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]); }).join('&');
        return fetch(apiBase + '?' + qs).then(function(r) { return r.json(); });
    }

    function apiPost(action, data) {
        data = data || {};
        data.action = action;
        return fetch(apiBase, {
            method: 'POST',
            headers: {'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken},
            body: JSON.stringify(data)
        }).then(function(r) { return r.json(); });
    }

    function severityBadge(severity) {
        var map = { critical: 'bg-danger', warning: 'bg-warning text-dark', info: 'bg-info' };
        return '<span class="badge ' + (map[severity] || 'bg-secondary') + '">' + escapeHtml(severity) + '</span>';
    }

    function statusBadge(status) {
        var map = { pending: 'bg-warning text-dark', processing: 'bg-info', completed: 'bg-success', failed: 'bg-danger' };
        return '<span class="badge ' + (map[status] || 'bg-secondary') + '">' + escapeHtml(status) + '</span>';
    }

    function escapeHtml(text) {
        if (!text) return '';
        var div = document.createElement('div');
        div.appendChild(document.createTextNode(text));
        return div.innerHTML;
    }

    function formatDate(dateStr) {
        if (!dateStr) return '-';
        var d = new Date(dateStr);
        if (isNaN(d.getTime())) return dateStr;
        return d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
    }

    // Note: innerHTML is used throughout this file to render trusted, server-derived
    // content (severity badges, pagination, table rows). All user-visible strings are
    // passed through escapeHtml() before insertion. No raw user input is rendered.

    // ========================
    // Issues
    // ========================

    function loadIssues(page) {
        page = page || 1;
        currentIssuesPage = page;
        var params = { page: page, per_page: perPage };
        var severity = byId('filter-severity') ? byId('filter-severity').value : '';
        var siteId = byId('filter-site') ? byId('filter-site').value : '';
        var status = byId('filter-status') ? byId('filter-status').value : '';
        if (severity) params.severity = severity;
        if (siteId) params.site_id = siteId;
        if (status) params.status = status;

        var tbody = byId('issues-tbody');
        if (tbody) tbody.innerHTML = '<tr><td colspan="8" class="text-center py-4"><i class="fa fa-spinner fa-spin"></i> Loading...</td></tr>';

        apiGet('issues', params).then(function(resp) {
            if (!resp.success || !resp.data) {
                if (tbody) tbody.innerHTML = '<tr><td colspan="8" class="text-center py-4 text-danger">Failed to load issues.</td></tr>';
                return;
            }
            var issues = resp.data.issues || [];
            var total = resp.data.total || 0;
            var totalPages = resp.data.total_pages || 1;
            // A fresh page of results invalidates any prior selection.
            selectedIssues.clear();
            updateBulkBar();
            if (issues.length === 0) {
                if (tbody) tbody.innerHTML = '<tr><td colspan="8" class="text-center py-4 text-muted">No issues found.</td></tr>';
                var cl = byId('issues-count-label'); if (cl) cl.textContent = '';
                var pg = byId('issues-pagination'); if (pg) pg.innerHTML = '';
                return;
            }
            var html = '';
            for (var i = 0; i < issues.length; i++) {
                var issue = issues[i];
                var rowClass = (issue.status === 'fixed' || issue.status === 'ignored') ? 'text-muted' : 'fw-bold';
                html += '<tr class="issue-row ' + rowClass + '" data-issue-id="' + issue.id + '" style="cursor:pointer;">';
                html += '<td><input type="checkbox" class="issue-select" value="' + issue.id + '" aria-label="Select issue ' + issue.id + '"></td>';
                html += '<td>' + severityBadge(issue.severity) + '</td>';
                html += '<td>' + escapeHtml(issue.title);
                if (issue.occurrence_count > 1) html += ' <span class="badge bg-secondary">' + issue.occurrence_count + 'x</span>';
                html += '</td>';
                html += '<td>' + escapeHtml(issue.primary_domain || issue.site_name || '') + '</td>';
                html += '<td>' + escapeHtml(issue.container_name || '-') + '</td>';
                html += '<td class="small">' + formatDate(issue.first_seen) + '</td>';
                html += '<td class="small">' + formatDate(issue.last_seen) + '</td>';
                html += '<td>';
                if (issue.status === 'fixed') html += '<span class="badge bg-success">Fixed</span>';
                else if (issue.status === 'ignored') html += '<span class="badge bg-warning">Ignored</span>';
                else if (issue.auto_resolved_at) html += '<span class="badge bg-info" title="System observed recovery">auto-resolved</span>';
                else html += '<span class="badge bg-primary">Open</span>';
                html += '</td></tr>';
            }
            if (tbody) tbody.innerHTML = html;
            var selAll = byId('issues-select-all');
            if (selAll) { selAll.checked = false; selAll.indeterminate = false; }
            var start = (page - 1) * perPage + 1;
            var end = Math.min(page * perPage, total);
            var countLabel = byId('issues-count-label');
            if (countLabel) countLabel.textContent = 'Showing ' + start + '-' + end + ' of ' + total + ' issues';
            renderPagination(totalPages, page);
        }).catch(function() {
            if (tbody) tbody.innerHTML = '<tr><td colspan="8" class="text-center py-4 text-danger">Failed to load issues.</td></tr>';
        });
    }

    function renderPagination(totalPages, currentPage) {
        var container = byId('issues-pagination');
        if (!container) return;
        if (totalPages <= 1) { container.innerHTML = ''; return; }
        var html = '';
        html += '<li class="page-item ' + (currentPage <= 1 ? 'disabled' : '') + '"><a class="page-link" href="#" data-page="' + (currentPage - 1) + '">&laquo;</a></li>';
        var startPage = Math.max(1, currentPage - 2), endPage = Math.min(totalPages, currentPage + 2);
        if (startPage > 1) {
            html += '<li class="page-item"><a class="page-link" href="#" data-page="1">1</a></li>';
            if (startPage > 2) html += '<li class="page-item disabled"><span class="page-link">...</span></li>';
        }
        for (var p = startPage; p <= endPage; p++) {
            html += '<li class="page-item ' + (p === currentPage ? 'active' : '') + '"><a class="page-link" href="#" data-page="' + p + '">' + p + '</a></li>';
        }
        if (endPage < totalPages) {
            if (endPage < totalPages - 1) html += '<li class="page-item disabled"><span class="page-link">...</span></li>';
            html += '<li class="page-item"><a class="page-link" href="#" data-page="' + totalPages + '">' + totalPages + '</a></li>';
        }
        html += '<li class="page-item ' + (currentPage >= totalPages ? 'disabled' : '') + '"><a class="page-link" href="#" data-page="' + (currentPage + 1) + '">&raquo;</a></li>';
        container.innerHTML = html;
    }

    function showIssueDetail(issueId) {
        apiGet('issues', {issue_id: issueId}).then(function(resp) {
            if (!resp.success || !resp.data || !resp.data.issue) return;
            var issue = resp.data.issue;
            byId('issueDetailTitle').textContent = issue.title;
            var sevBadge = byId('issueDetailSeverity');
            sevBadge.className = 'badge ' + (issue.severity === 'critical' ? 'bg-danger' : issue.severity === 'warning' ? 'bg-warning text-dark' : 'bg-info');
            sevBadge.textContent = issue.severity;
            byId('issueDetailCategory').textContent = issue.category || '';
            byId('issueDetailMeta').textContent =
                (issue.primary_domain || issue.site_name || '') +
                (issue.container_name ? ' / ' + issue.container_name : '') +
                ' | Occurrences: ' + (issue.occurrence_count || 1) +
                ' | First: ' + formatDate(issue.first_seen) + ' | Last: ' + formatDate(issue.last_seen);
            byId('issueDetailDiagnosis').textContent = issue.diagnosis || '';
            byId('issueDetailRecommendation').textContent = issue.recommendation || '';

            // Auto-fix panel: shows the AI-suggested wp-cli command, an
            // "Apply this fix" button when status is 'available', a
            // running spinner during execution, or the captured output
            // once applied/failed.
            renderAutoFixPanel(issue);

            // Evidence panel: shows the verbatim log line the AI cited as
            // proof, plus the timestamp parsed from that line. If the
            // observed timestamp is older than 7 days, warn the user.
            var evidenceWrap = byId('issueDetailEvidenceWrap');
            if (issue.evidence) {
                byId('issueDetailEvidence').textContent = issue.evidence;
                var metaParts = [];
                if (issue.evidence_observed_at) {
                    metaParts.push('Log line timestamp: ' + formatDate(issue.evidence_observed_at));
                } else {
                    metaParts.push('Log line timestamp not available');
                }
                if (issue.last_seen) {
                    metaParts.push('Last detected: ' + formatDate(issue.last_seen));
                }
                byId('issueDetailEvidenceMeta').textContent = metaParts.join('  ·  ');

                var staleEl  = byId('issueDetailStaleWarning');
                var reasonEl = byId('issueDetailStaleReason');
                var observedTs = issue.evidence_observed_at ? Date.parse(issue.evidence_observed_at.replace(' ', 'T')) : NaN;
                var ageDays = isNaN(observedTs) ? null : Math.floor((Date.now() - observedTs) / 86400000);
                if (ageDays !== null && ageDays >= 7) {
                    reasonEl.textContent = ' The log line evidencing this issue is from '
                        + ageDays + ' day(s) ago. Verify the issue is still happening before acting.';
                    staleEl.classList.remove('d-none');
                } else {
                    staleEl.classList.add('d-none');
                }
                evidenceWrap.classList.remove('d-none');
            } else {
                evidenceWrap.classList.add('d-none');
            }

            var logWrap = byId('issueDetailLogWrap');
            if (issue.raw_log_excerpt) { byId('issueDetailLog').textContent = issue.raw_log_excerpt; logWrap.classList.remove('d-none'); }
            else { logWrap.classList.add('d-none'); }
            qa('.btn-mark-fixed-modal').forEach(function(b) { b.setAttribute('data-issue-id', issue.id); });
            qa('.btn-mark-ignored-modal').forEach(function(b) { b.setAttribute('data-issue-id', issue.id); });
            qa('.btn-explain-modal').forEach(function(b) {
                b.setAttribute('data-issue-id', issue.id);
                b.disabled = false;
                b.innerHTML = '<i class="fa fa-comments-o"></i> Explain this to me';
            });
            // Show cached explanation if present; otherwise hide the wrap until user clicks.
            var explainWrap = byId('issueDetailExplainWrap');
            var explainBody = byId('issueDetailExplain');
            var explainMeta = byId('issueDetailExplainMeta');
            if (issue.user_explanation) {
                explainBody.textContent = issue.user_explanation;
                explainMeta.textContent = 'Generated ' + (issue.user_explanation_at || '')
                    + (issue.user_explanation_model ? ' · ' + issue.user_explanation_model : '');
                explainWrap.classList.remove('d-none');
            } else {
                explainBody.textContent = '';
                explainMeta.textContent = '';
                explainWrap.classList.add('d-none');
            }
            var isClosed = (issue.status === 'fixed' || issue.status === 'ignored');
            qa('.btn-mark-fixed-modal').forEach(function(b) { b.classList.toggle('d-none', isClosed); });
            qa('.btn-mark-ignored-modal').forEach(function(b) { b.classList.toggle('d-none', isClosed); });
            var modal = new bootstrap.Modal(byId('issueDetailModal'));
            modal.show();
        });
    }

    function updateBulkBar() {
        var bar = byId('issues-bulk-bar');
        var countEl = byId('issues-selected-count');
        var n = selectedIssues.size;
        if (countEl) countEl.textContent = n;
        if (bar) bar.style.display = n > 0 ? 'block' : 'none';
        // Sync the header checkbox's tri-state.
        var selAll = byId('issues-select-all');
        if (selAll) {
            var rowChecks = qa('.issue-select');
            var visible = rowChecks.length;
            var checkedVisible = 0;
            rowChecks.forEach(function(cb) { if (cb.checked) checkedVisible++; });
            selAll.checked = (visible > 0 && checkedVisible === visible);
            selAll.indeterminate = (checkedVisible > 0 && checkedVisible < visible);
        }
    }

    function clearIssueSelection() {
        selectedIssues.clear();
        qa('.issue-select').forEach(function(cb) { cb.checked = false; });
        updateBulkBar();
    }

    function bulkIssueAction(action, confirmMsg) {
        var n = selectedIssues.size;
        if (n === 0) return;
        if (confirmMsg && !confirm(confirmMsg.replace('{n}', n))) return;
        apiPost(action, { issue_ids: Array.from(selectedIssues) }).then(function(resp) {
            if (!resp.success) {
                alert('Action failed: ' + (resp.error || 'unknown'));
                return;
            }
            var affected = (resp.data && resp.data.affected) || 0;
            var requested = (resp.data && resp.data.requested) || n;
            if (affected < requested) {
                // Some rows were skipped (already in target state, or another
                // user's row that root didn't touch).
                console.info('bulk ' + action + ': ' + affected + '/' + requested + ' rows changed');
            }
            clearIssueSelection();
            loadIssues(currentIssuesPage);
        }).catch(function(err) {
            alert('Request failed: ' + err);
        });
    }

    function markIssue(action, issueId) {
        apiPost(action, {issue_id: issueId}).then(function(resp) {
            if (resp.success) {
                var modalInst = bootstrap.Modal.getInstance(byId('issueDetailModal'));
                if (modalInst) modalInst.hide();
                loadIssues(currentIssuesPage);
            } else { alert(resp.error || 'Failed to update issue.'); }
        }).catch(function() { alert('Failed to update issue.'); });
    }

    // Render the AI-suggested auto-fix card inside the issue detail modal.
    // Visibility and controls vary by issue.auto_fix_status:
    //   available -> show "Apply this fix" button
    //   running   -> show "Applying..." with disabled button
    //   applied   -> show success badge + collapsed command output
    //   failed    -> show failure badge + collapsed command output
    //   none / no command -> hide the panel entirely
    //
    // All values inserted into the DOM here are either trusted static
    // markup (badge templates) or routed through textContent. No raw
    // server strings are passed to innerHTML.
    function renderAutoFixPanel(issue) {
        var wrap         = byId('issueDetailAutoFixWrap');
        var cmdEl        = byId('issueDetailAutoFixCommand');
        var descEl       = byId('issueDetailAutoFixDescription');
        var statusEl     = byId('issueDetailAutoFixStatus');
        var outputWrap   = byId('issueDetailAutoFixOutputWrap');
        var outputEl     = byId('issueDetailAutoFixOutput');
        var btn          = wrap ? wrap.querySelector('.btn-apply-fix-modal') : null;

        if (!wrap || !btn) return;

        var hasFix = issue.auto_fix_command && issue.auto_fix_status && issue.auto_fix_status !== 'none';
        if (!hasFix) {
            wrap.classList.add('d-none');
            return;
        }

        cmdEl.textContent  = issue.auto_fix_command;
        descEl.textContent = issue.auto_fix_description || '';
        btn.setAttribute('data-issue-id', issue.id);

        // Reset state.
        btn.disabled = false;
        btn.classList.remove('d-none');
        // Static template — no untrusted content.
        setTrustedHtml(btn, '<i class="fa fa-play"></i> Apply this fix');
        statusEl.textContent = '';
        statusEl.className = 'small text-muted';
        outputWrap.classList.add('d-none');
        outputEl.textContent = '';

        switch (issue.auto_fix_status) {
            case 'available':
                // Default reset state already shows the button.
                break;
            case 'running':
                btn.disabled = true;
                setTrustedHtml(btn, '<span class="spinner-border spinner-border-sm me-1"></span>Applying…');
                statusEl.textContent = 'Running command…';
                break;
            case 'applied':
                btn.classList.add('d-none');
                // Build success badge with safe DOM ops.
                statusEl.textContent = '';
                appendBadge(statusEl, 'bg-success', 'fa-check', 'Applied');
                if (issue.auto_fix_applied_at) {
                    var when = document.createElement('span');
                    when.className = 'text-muted ms-1';
                    when.textContent = 'at ' + issue.auto_fix_applied_at;
                    statusEl.appendChild(when);
                }
                if (issue.auto_fix_output) {
                    outputEl.textContent = issue.auto_fix_output;
                    outputWrap.classList.remove('d-none');
                }
                break;
            case 'failed':
                btn.classList.add('d-none');
                statusEl.textContent = '';
                appendBadge(statusEl, 'bg-danger', 'fa-times', 'Failed');
                if (issue.auto_fix_output) {
                    outputEl.textContent = issue.auto_fix_output;
                    outputWrap.classList.remove('d-none');
                }
                break;
        }
        wrap.classList.remove('d-none');
    }

    // Helper: assign a fixed, source-controlled HTML template. Centralised
    // so the security review can audit every callsite easily — no untrusted
    // content ever flows in here.
    function setTrustedHtml(el, html) {
        el.innerHTML = html;
    }

    // Helper: append a Bootstrap badge with an icon and a text label.
    // The label is set via textContent so server / AI strings are safe.
    function appendBadge(parent, bgClass, iconClass, label) {
        var span = document.createElement('span');
        span.className = 'badge ' + bgClass;
        var icon = document.createElement('i');
        icon.className = 'fa ' + iconClass;
        span.appendChild(icon);
        span.appendChild(document.createTextNode(' ' + label));
        parent.appendChild(span);
    }

    // Apply an AI-suggested wp-cli fix. Disables the button while in flight,
    // surfaces the captured output on completion, and refreshes the issue
    // list (the issue should flip to 'fixed' on success).
    function applyAutoFix(issueId) {
        var wrap     = byId('issueDetailAutoFixWrap');
        var btn      = wrap ? wrap.querySelector('.btn-apply-fix-modal') : null;
        var statusEl = byId('issueDetailAutoFixStatus');
        var outputWrap = byId('issueDetailAutoFixOutputWrap');
        var outputEl   = byId('issueDetailAutoFixOutput');
        if (!btn) return;

        btn.disabled = true;
        setTrustedHtml(btn, '<span class="spinner-border spinner-border-sm me-1"></span>Applying…');
        if (statusEl) {
            statusEl.textContent = 'Running command…';
            statusEl.className = 'small text-muted';
        }

        apiPost('apply_auto_fix', {issue_id: parseInt(issueId, 10)}).then(function(resp) {
            if (resp.success) {
                if (statusEl) {
                    statusEl.textContent = '';
                    appendBadge(statusEl, 'bg-success', 'fa-check', 'Applied');
                }
                btn.classList.add('d-none');
                if (resp.data && resp.data.output) {
                    outputEl.textContent = resp.data.output;
                    outputWrap.classList.remove('d-none');
                }
                // Refresh list so the issue flips to fixed.
                loadIssues(currentIssuesPage);
                // Close modal after a short pause so the user sees the success state.
                setTimeout(function() {
                    var modalInst = bootstrap.Modal.getInstance(byId('issueDetailModal'));
                    if (modalInst) modalInst.hide();
                }, 1500);
            } else {
                btn.disabled = false;
                setTrustedHtml(btn, '<i class="fa fa-play"></i> Apply this fix');
                if (statusEl) {
                    statusEl.textContent = '';
                    appendBadge(statusEl, 'bg-danger', 'fa-times', 'Failed');
                    var err = document.createElement('span');
                    err.className = 'text-muted ms-1';
                    err.textContent = resp.error || 'unknown error';
                    statusEl.appendChild(err);
                }
                if (resp.data && resp.data.output) {
                    outputEl.textContent = resp.data.output;
                    outputWrap.classList.remove('d-none');
                }
            }
        }).catch(function() {
            btn.disabled = false;
            setTrustedHtml(btn, '<i class="fa fa-play"></i> Apply this fix');
            if (statusEl) {
                statusEl.textContent = '';
                appendBadge(statusEl, 'bg-danger', 'fa-times', 'Request failed');
            }
        });
    }

    function markIssueIgnored(issueId, suppress, reason) {
        apiPost('mark_ignored', {
            issue_id: parseInt(issueId, 10),
            suppress: suppress ? 1 : 0,
            reason: reason || ''
        }).then(function(data) {
            if (!data.success) {
                alert('Failed to ignore: ' + (data.error || 'unknown'));
                return;
            }
            var modalInst = bootstrap.Modal.getInstance(byId('issueDetailModal'));
            if (modalInst) modalInst.hide();
            loadIssues(currentIssuesPage);
        }).catch(function(err) {
            alert('Request failed: ' + err);
        });
    }

    function explainIssue(issueId, btn) {
        var wrap = byId('issueDetailExplainWrap');
        var body = byId('issueDetailExplain');
        var meta = byId('issueDetailExplainMeta');
        if (btn) {
            btn.disabled = true;
            btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Thinking…';
        }
        apiPost('explain_issue', { issue_id: parseInt(issueId, 10) })
            .then(function(data) {
                if (!data.success) {
                    alert('Failed to generate explanation: ' + (data.error || 'unknown'));
                    return;
                }
                var payload = data.data || data;
                body.textContent = payload.explanation || '';
                meta.textContent = (payload.cached ? 'Cached explanation' : 'Newly generated')
                    + (payload.generated_at ? ' · ' + payload.generated_at : '')
                    + (payload.model ? ' · ' + payload.model : '');
                wrap.classList.remove('d-none');
            })
            .catch(function(err) { alert('Request failed: ' + err); })
            .finally(function() {
                if (btn) {
                    btn.disabled = false;
                    btn.innerHTML = '<i class="fa fa-comments-o"></i> Explain this to me';
                }
            });
    }

    // ========================
    // Log Reviews
    // ========================

    function loadReviewRemaining() {
        qa('.review-remaining').forEach(function(elem) {
            var siteId = elem.getAttribute('data-site-id');
            var dailyLimit = parseInt(elem.getAttribute('data-daily-limit'), 10) || 3;

            // Root has no limits - just check for in-progress reviews
            if (isRoot) {
                elem.textContent = 'Unlimited (admin)';
                apiGet('review_history', {site_id: siteId}).then(function(resp) {
                    if (!resp.success) return;
                    var reviews = resp.data.reviews || [];
                    for (var j = 0; j < reviews.length; j++) {
                        if (reviews[j].status === 'pending' || reviews[j].status === 'processing') {
                            showReviewProgress(siteId, reviews[j].id);
                            break;
                        }
                    }
                });
                return;
            }

            apiGet('review_history', {site_id: siteId}).then(function(resp) {
                if (!resp.success) return;
                var reviews = resp.data.reviews || [];
                var today = new Date().toISOString().slice(0, 10);
                var todayCount = 0;
                for (var i = 0; i < reviews.length; i++) {
                    // Only count the user's own reviews against the limit (not root's)
                    if (reviews[i].requested_at && reviews[i].requested_at.slice(0, 10) === today
                        && reviews[i].username !== 'root') {
                        todayCount++;
                    }
                }
                var remaining = Math.max(0, dailyLimit - todayCount);
                elem.textContent = remaining + ' of ' + dailyLimit + ' reviews remaining today';
                var cardBody = elem.closest('.card-body');
                var btn = cardBody ? cardBody.querySelector('.btn-request-review') : null;
                if (btn) { btn.disabled = remaining <= 0; btn.title = remaining <= 0 ? 'Daily limit reached' : ''; }
                for (var j = 0; j < reviews.length; j++) {
                    if (reviews[j].status === 'pending' || reviews[j].status === 'processing') {
                        showReviewProgress(siteId, reviews[j].id);
                        break;
                    }
                }
            });
        });
    }

    function requestReview(siteId) {
        var btn = q('.btn-request-review[data-site-id="' + siteId + '"]');
        if (btn) { btn.disabled = true; btn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Requesting...'; }
        apiPost('request_review', {site_id: siteId, request_type: 'full'}).then(function(resp) {
            if (resp.success) { showReviewProgress(siteId, resp.data.review_id); loadReviewRemaining(); loadReviewHistory(); }
            else { alert(resp.error || 'Failed to request review.'); if (btn) { btn.disabled = false; btn.innerHTML = '<i class="fa fa-search"></i> Request Log Review'; } }
        }).catch(function() { alert('Failed to request review.'); if (btn) { btn.disabled = false; btn.innerHTML = '<i class="fa fa-search"></i> Request Log Review'; } });
    }

    function showReviewProgress(siteId, reviewId) {
        var progressEl = q('.review-progress[data-site-id="' + siteId + '"]');
        if (progressEl) progressEl.classList.remove('d-none');
        if (pollTimers['review_' + reviewId]) clearInterval(pollTimers['review_' + reviewId]);
        pollTimers['review_' + reviewId] = setInterval(function() {
            apiGet('review_status', {id: reviewId}).then(function(resp) {
                if (!resp.success) return;
                var review = resp.data.review;
                if (review.status === 'completed' || review.status === 'failed') {
                    clearInterval(pollTimers['review_' + reviewId]);
                    delete pollTimers['review_' + reviewId];
                    if (progressEl) progressEl.classList.add('d-none');
                    var btn = q('.btn-request-review[data-site-id="' + siteId + '"]');
                    if (btn) { btn.disabled = false; btn.innerHTML = '<i class="fa fa-search"></i> Request Log Review'; }
                    loadReviewRemaining();
                    loadReviewHistory();
                }
            });
        }, 5000);
    }

    function loadReviewHistory() {
        var siteSelect = byId('review-history-site');
        var siteId = siteSelect ? siteSelect.value : '';
        var params = {};
        if (siteId) params.site_id = siteId;
        var tbody = byId('review-history-tbody');
        if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="text-center py-4"><i class="fa fa-spinner fa-spin"></i> Loading...</td></tr>';
        apiGet('review_history', params).then(function(resp) {
            if (!resp.success || !resp.data) { if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="text-center py-4 text-danger">Failed to load review history.</td></tr>'; return; }
            var reviews = resp.data.reviews || [];
            if (reviews.length === 0) { if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="text-center py-4 text-muted">No reviews yet.</td></tr>'; return; }
            var html = '';
            for (var i = 0; i < reviews.length; i++) {
                var r = reviews[i];
                html += '<tr>';
                html += '<td class="small">' + formatDate(r.requested_at) + '</td>';
                html += '<td>' + escapeHtml(r.primary_domain || '') + '</td>';
                html += '<td>' + escapeHtml(r.request_type || 'full') + '</td>';
                html += '<td>' + statusBadge(r.status) + '</td>';
                html += '<td class="text-truncate" style="max-width: 300px;">' + escapeHtml(r.result_summary || (r.status === 'pending' || r.status === 'processing' ? 'In progress...' : '-')) + '</td>';
                html += '<td>';
                if (r.status === 'completed') html += '<button type="button" class="btn btn-sm btn-outline-primary btn-view-review me-1" data-review-id="' + r.id + '" title="View details"><i class="fa fa-eye"></i></button>';
                else if (r.status === 'pending' || r.status === 'processing') html += '<i class="fa fa-spinner fa-spin text-muted me-1"></i>';
                if (r.status === 'completed' || r.status === 'failed') {
                    html += '<button type="button" class="btn btn-sm btn-outline-danger btn-delete-review" data-review-id="' + r.id + '" title="Delete review"><i class="fa fa-trash-o"></i></button>';
                }
                html += '</td></tr>';
            }
            if (tbody) tbody.innerHTML = html;
        }).catch(function() { if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="text-center py-4 text-danger">Failed to load review history.</td></tr>'; });
    }

    function showReviewDetail(reviewId) {
        apiGet('review_status', {id: reviewId}).then(function(resp) {
            if (!resp.success || !resp.data || !resp.data.review) return;
            var review = resp.data.review;
            var html = '';

            // Status and meta header
            html += '<div class="d-flex align-items-center gap-2 mb-3">';
            html += statusBadge(review.status);
            html += ' <span class="text-muted small">Requested: ' + formatDate(review.requested_at) + '</span>';
            if (review.completed_at) html += ' <span class="text-muted small">Completed: ' + formatDate(review.completed_at) + '</span>';
            html += '</div>';

            // Parse result_details
            var details = review.result_details;
            if (typeof details === 'string') { try { details = JSON.parse(details); } catch (e) { details = null; } }

            // Traffic analysis short-circuits the log-review renderer below — it has
            // no findings/site_status/pages_checked, just a free-text AI write-up.
            if (review.review_type === 'traffic' || (details && details.traffic_analysis)) {
                var analysisText = (details && details.traffic_analysis) ? details.traffic_analysis : (review.result_summary || '');
                html += '<div class="mb-3"><strong><i class="fa fa-line-chart me-1"></i>Traffic Analysis</strong>';
                html += '<div class="mt-2 p-3 border rounded" style="white-space:pre-wrap; font-family:inherit;">' + escapeHtml(analysisText) + '</div></div>';
                $('#reviewDetailModalBody').html(html);
                return;
            }

            // Site status banner (from the new AI prompt structure)
            if (details && details.site_status) {
                var ss = details.site_status;
                var statusClass = ss.is_loading === true ? 'alert-success' : ss.is_loading === false ? 'alert-danger' : 'alert-warning';
                var statusIcon = ss.is_loading === true ? 'fa-check-circle' : ss.is_loading === false ? 'fa-times-circle' : 'fa-question-circle';
                var statusLabel = ss.is_loading === true ? 'Site is Loading' : ss.is_loading === false ? 'Site May Not Be Loading' : 'Unable to Determine';
                html += '<div class="alert ' + statusClass + ' d-flex align-items-start">';
                html += '<i class="fa ' + statusIcon + ' me-3 mt-1" style="font-size:1.4em;"></i>';
                html += '<div><strong>' + statusLabel + '</strong>';
                if (ss.confidence) html += ' <span class="badge bg-secondary">' + escapeHtml(ss.confidence) + ' confidence</span>';
                if (ss.explanation) html += '<br><span class="text-muted small">' + escapeHtml(ss.explanation) + '</span>';
                html += '</div></div>';
            }

            // Live site check results
            if (details && details.initial_site_check) {
                var sc = details.initial_site_check;
                var scClass = (sc.http_code >= 200 && sc.http_code < 400) ? 'text-success' : sc.http_code >= 400 ? 'text-danger' : 'text-warning';
                html += '<div class="mb-3 small"><strong><i class="fa fa-globe me-1"></i>Live Site Check</strong>';
                html += '<table class="table table-sm table-borderless mb-0 mt-1" style="max-width:400px;">';
                html += '<tr><td class="text-muted py-0">URL:</td><td class="py-0">' + escapeHtml(sc.url) + '</td></tr>';
                html += '<tr><td class="text-muted py-0">Status:</td><td class="py-0 ' + scClass + '"><strong>' + sc.http_code + '</strong></td></tr>';
                html += '<tr><td class="text-muted py-0">Response Time:</td><td class="py-0">' + sc.time_total + 's</td></tr>';
                if (sc.error) html += '<tr><td class="text-muted py-0">Error:</td><td class="py-0 text-danger">' + escapeHtml(sc.error) + '</td></tr>';
                html += '</table></div>';
            }

            // Additional page check results
            if (details && details.pages_checked && details.pages_checked.length > 0) {
                html += '<div class="mb-3 small"><strong><i class="fa fa-files-o me-1"></i>Additional Page Checks</strong>';
                html += '<table class="table table-sm table-bordered mt-1">';
                html += '<thead><tr><th>Path</th><th>Status</th><th>Time</th><th>Size</th></tr></thead><tbody>';
                for (var p = 0; p < details.pages_checked.length; p++) {
                    var pc = details.pages_checked[p];
                    var pcClass = (pc.http_code >= 200 && pc.http_code < 400) ? 'text-success' : pc.http_code >= 400 ? 'text-danger' : 'text-warning';
                    html += '<tr>';
                    html += '<td>' + escapeHtml(pc.path) + '</td>';
                    html += '<td class="' + pcClass + '"><strong>' + (pc.http_code || 'failed') + '</strong></td>';
                    html += '<td>' + pc.time_total + 's</td>';
                    html += '<td>' + (pc.size_download ? pc.size_download + ' bytes' : '-') + '</td>';
                    html += '</tr>';
                }
                html += '</tbody></table></div>';
            }

            // Summary
            if (review.result_summary) {
                html += '<div class="mb-3"><strong>Summary</strong><p class="mb-0">' + escapeHtml(review.result_summary) + '</p></div>';
            }

            // Overall health badge
            if (details && details.overall_health) {
                var healthMap = { healthy: 'bg-success', degraded: 'bg-warning text-dark', critical: 'bg-danger' };
                html += '<div class="mb-3"><strong>Overall Health:</strong> ';
                html += '<span class="badge ' + (healthMap[details.overall_health] || 'bg-secondary') + '">';
                html += escapeHtml(details.overall_health.charAt(0).toUpperCase() + details.overall_health.slice(1)) + '</span></div>';
            }

            // Findings
            if (details && details.findings && details.findings.length > 0) {
                html += '<strong>Findings (' + details.findings.length + ')</strong>';
                html += '<div class="mt-2">';
                for (var i = 0; i < details.findings.length; i++) {
                    var f = details.findings[i];
                    var borderClass = f.severity === 'critical' ? 'border-danger' : f.severity === 'warning' ? 'border-warning' : 'border-info';
                    var impactLabel = '';
                    if (f.impact) {
                        var impactMap = { site_down: 'Site Down', partial_outage: 'Partial Outage', degraded: 'Degraded', cosmetic: 'Cosmetic' };
                        var impactBg = { site_down: 'bg-danger', partial_outage: 'bg-warning text-dark', degraded: 'bg-info', cosmetic: 'bg-secondary' };
                        impactLabel = ' <span class="badge ' + (impactBg[f.impact] || 'bg-secondary') + '">' + (impactMap[f.impact] || escapeHtml(f.impact)) + '</span>';
                    }
                    html += '<div class="card mb-2 border-start border-4 ' + borderClass + '">';
                    html += '<div class="card-body py-2 px-3">';
                    html += '<div class="d-flex align-items-center gap-2 mb-1">';
                    html += severityBadge(f.severity);
                    html += impactLabel;
                    if (f.affects_loading) html += ' <span class="badge bg-dark">Affects Loading</span>';
                    html += ' <strong>' + escapeHtml(f.title) + '</strong>';
                    html += '</div>';
                    if (f.description) html += '<p class="mb-1 small">' + escapeHtml(f.description) + '</p>';
                    if (f.recommendation) html += '<p class="mb-0 small text-primary"><i class="fa fa-lightbulb-o me-1"></i>' + escapeHtml(f.recommendation) + '</p>';
                    html += '</div></div>';
                }
                html += '</div>';
            } else if (details) {
                html += '<p class="text-success"><i class="fa fa-check-circle me-1"></i>No issues found - your site looks healthy.</p>';
            }

            // Model/token info - only shown if present (root only, API strips these for non-root)
            if (review.ai_model_used) {
                html += '<hr><p class="text-muted small mb-0">Model: ' + escapeHtml(review.ai_model_used);
                if (review.ai_tokens_used) html += ' | Tokens: ' + review.ai_tokens_used;
                html += '</p>';
            }

            // Error message if failed
            if (review.error_message) {
                html += '<div class="alert alert-danger mt-2">' + escapeHtml(review.error_message) + '</div>';
            }

            byId('reviewDetailBody').innerHTML = html;
            var modal = new bootstrap.Modal(byId('reviewDetailModal'));
            modal.show();
        });
    }

    function deleteReview(reviewId) {
        apiPost('delete_review', {review_id: reviewId}).then(function(resp) {
            if (resp.success) {
                loadReviewHistory();
                loadReviewRemaining();
            } else {
                alert(resp.error || 'Failed to delete review.');
            }
        }).catch(function() { alert('Failed to delete review.'); });
    }

    // ========================
    // Notification Settings
    // ========================

    function saveNotificationEmail(subscriptionId) {
        var input = q('.notification-email[data-subscription-id="' + subscriptionId + '"]');
        var statusEl = q('.notification-save-status[data-subscription-id="' + subscriptionId + '"]');
        var email = input ? input.value.trim() : '';
        if (statusEl) statusEl.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Saving...';
        apiPost('update_notification_email', { subscription_id: subscriptionId, notification_email: email }).then(function(resp) {
            if (resp.success) {
                if (statusEl) statusEl.innerHTML = '<span class="text-success"><i class="fa fa-check"></i> Saved</span>';
                setTimeout(function() { if (statusEl) statusEl.textContent = ''; }, 3000);
            } else {
                if (statusEl) statusEl.innerHTML = '<span class="text-danger"><i class="fa fa-times"></i> ' + escapeHtml(resp.error || 'Failed') + '</span>';
            }
        }).catch(function() {
            if (statusEl) statusEl.innerHTML = '<span class="text-danger"><i class="fa fa-times"></i> Request failed</span>';
        });
    }

    // ========================
    // On-Demand Traffic Analysis
    // ========================

    // Enqueue a traffic-type review via the dedicated endpoint. The button
    // sits inside the per-subscription Traffic panel; on success we disable
    // it and show a confirmation. The existing review-history table picks
    // up the result on its next load.
    //
    // Button label HTML is built from a fixed template (no untrusted data);
    // status text is set via textContent.
    function requestTrafficAnalysis(btn) {
        if (!btn || btn.disabled) return;
        var subscriptionId = parseInt(btn.getAttribute('data-subscription-id'), 10) || 0;
        if (!subscriptionId) return;
        var statusEl = q('.analyze-traffic-status[data-subscription-id="' + subscriptionId + '"]');
        var origHtml = btn.innerHTML;
        btn.disabled = true;
        setTrustedHtml(btn, '<span class="spinner-border spinner-border-sm me-1"></span>Queuing…');
        if (statusEl) { statusEl.textContent = ''; statusEl.className = 'analyze-traffic-status text-muted small'; }

        fetch('api/ai-monitor-request-traffic-analysis.php', {
            method: 'POST',
            headers: {'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken},
            body: JSON.stringify({ subscription_id: subscriptionId })
        }).then(function(r) { return r.json(); }).then(function(resp) {
            if (resp && resp.ok) {
                setTrustedHtml(btn, '<i class="fa fa-check"></i> Queued');
                if (statusEl) {
                    statusEl.textContent = 'Traffic analysis #' + resp.queued_review_id + ' queued. It will appear in Review History when complete.';
                    statusEl.className = 'analyze-traffic-status text-success small';
                }
                loadReviewHistory();
            } else {
                btn.disabled = false;
                setTrustedHtml(btn, origHtml);
                if (statusEl) {
                    statusEl.textContent = (resp && resp.error) ? String(resp.error) : 'Failed to queue traffic analysis.';
                    statusEl.className = 'analyze-traffic-status text-danger small';
                }
            }
        }).catch(function() {
            btn.disabled = false;
            setTrustedHtml(btn, origHtml);
            if (statusEl) {
                statusEl.textContent = 'Request failed. Please try again.';
                statusEl.className = 'analyze-traffic-status text-danger small';
            }
        });
    }

    // ========================
    // Event Bindings
    // ========================

    document.addEventListener('DOMContentLoaded', function() {
        loadIssues(1);
        loadReviewRemaining();
        loadReviewHistory();

        ['filter-severity', 'filter-site', 'filter-status'].forEach(function(id) {
            var elem = byId(id);
            if (elem) elem.addEventListener('change', function() { loadIssues(1); });
        });

        // Deep-link from email: ?review_id=N opens that review's modal;
        // ?site_id=N pre-filters the issues list to that site.
        var qs = new URLSearchParams(window.location.search);
        var deepReviewId = parseInt(qs.get('review_id') || '0', 10);
        var deepSiteId = parseInt(qs.get('site_id') || '0', 10);
        if (deepReviewId > 0) {
            setTimeout(function() { showReviewDetail(deepReviewId); }, 250);
        }
        if (deepSiteId > 0) {
            var siteFilter = byId('filter-site');
            if (siteFilter) {
                siteFilter.value = String(deepSiteId);
                loadIssues(1);
            }
        }

        document.addEventListener('click', function(e) {
            var pageLink = e.target.closest('#issues-pagination .page-link');
            if (pageLink) { e.preventDefault(); var page = parseInt(pageLink.getAttribute('data-page'), 10); if (page >= 1) loadIssues(page); return; }
            // Checkbox clicks must not bubble into the row-click handler
            // that opens the detail modal.
            if (e.target.classList.contains('issue-select')) {
                var id = parseInt(e.target.value, 10);
                if (e.target.checked) selectedIssues.add(id);
                else selectedIssues.delete(id);
                updateBulkBar();
                e.stopPropagation();
                return;
            }
            if (e.target.id === 'issues-select-all') {
                var on = e.target.checked;
                qa('.issue-select').forEach(function(cb) {
                    cb.checked = on;
                    var id = parseInt(cb.value, 10);
                    if (on) selectedIssues.add(id);
                    else selectedIssues.delete(id);
                });
                updateBulkBar();
                return;
            }
            if (e.target.closest('#bulk-fix-issues')) {
                bulkIssueAction('bulk_mark_fixed', 'Mark {n} issue(s) as fixed?');
                return;
            }
            if (e.target.closest('#bulk-ignore-issues')) {
                bulkIssueAction('bulk_mark_ignored', 'Ignore {n} issue(s) and suppress future matches?');
                return;
            }
            if (e.target.closest('#bulk-delete-issues')) {
                bulkIssueAction('bulk_delete', 'Permanently delete {n} issue(s)? This cannot be undone.');
                return;
            }
            if (e.target.closest('#bulk-clear-issues')) {
                clearIssueSelection();
                return;
            }
            var issueRow = e.target.closest('.issue-row');
            if (issueRow) {
                // Ignore clicks on the checkbox cell (handled above).
                if (e.target.closest('.issue-select')) return;
                showIssueDetail(issueRow.getAttribute('data-issue-id'));
                return;
            }
            var applyFixBtn = e.target.closest('.btn-apply-fix-modal');
            if (applyFixBtn) {
                var afid = applyFixBtn.getAttribute('data-issue-id');
                if (afid) applyAutoFix(afid);
                return;
            }
            var markFixedBtn = e.target.closest('.btn-mark-fixed-modal');
            if (markFixedBtn) {
                var fid = markFixedBtn.getAttribute('data-issue-id');
                if (fid) markIssue('mark_fixed', fid);
                return;
            }
            var markIgnoredBtn = e.target.closest('.btn-mark-ignored-modal');
            if (markIgnoredBtn) {
                var iid = markIgnoredBtn.getAttribute('data-issue-id');
                if (!iid) return;
                // Silent ignore: always suppress future matches, no prompt.
                // User can un-suppress from the Suppressed Alerts panel.
                markIssueIgnored(iid, true, '');
                return;
            }
            var explainBtn = e.target.closest('.btn-explain-modal');
            if (explainBtn) {
                var eid = explainBtn.getAttribute('data-issue-id');
                if (!eid) return;
                explainIssue(eid, explainBtn);
                return;
            }
            var reviewBtn = e.target.closest('.btn-request-review');
            if (reviewBtn) { requestReview(reviewBtn.getAttribute('data-site-id')); return; }
            var viewReviewBtn = e.target.closest('.btn-view-review');
            if (viewReviewBtn) { showReviewDetail(viewReviewBtn.getAttribute('data-review-id')); return; }
            var deleteReviewBtn = e.target.closest('.btn-delete-review');
            if (deleteReviewBtn) {
                if (confirm('Delete this review? This cannot be undone.')) {
                    deleteReview(deleteReviewBtn.getAttribute('data-review-id'));
                }
                return;
            }
            var saveNotiBtn = e.target.closest('.btn-save-notification');
            if (saveNotiBtn) { saveNotificationEmail(saveNotiBtn.getAttribute('data-subscription-id')); return; }
            var analyzeBtn = e.target.closest('.btn-analyze-traffic');
            if (analyzeBtn) {
                requestTrafficAnalysis(analyzeBtn);
                return;
            }
            var subCard = e.target.closest('.subscription-card');
            if (subCard) {
                var filterSite = byId('filter-site');
                if (filterSite) filterSite.value = subCard.getAttribute('data-site-id');
                loadIssues(1);
                var issuesTable = byId('issues-table');
                if (issuesTable) issuesTable.scrollIntoView({behavior: 'smooth', block: 'start'});
                return;
            }
        });

        document.addEventListener('keydown', function(e) {
            if (e.key === 'Enter' && e.target.closest('.notification-email')) {
                e.preventDefault();
                saveNotificationEmail(e.target.getAttribute('data-subscription-id'));
            }
        });

        var reviewSiteSelect = byId('review-history-site');
        if (reviewSiteSelect) reviewSiteSelect.addEventListener('change', function() { loadReviewHistory(); });
    });

    window.addEventListener('beforeunload', function() {
        for (var key in pollTimers) clearInterval(pollTimers[key]);
    });
})();

(function() {
    var suppBody = document.getElementById('suppressionsBody');
    if (!suppBody) return;

    var csrfToken = <?php echo json_encode($csrf_token); ?>;

    function makeCell(text) {
        var td = document.createElement('td');
        td.textContent = text != null ? String(text) : '';
        return td;
    }

    function loadSuppressions() {
        fetch('api/ai-monitor-customer.php?action=list_suppressions')
            .then(function(r) { return r.json(); })
            .then(function(data) {
                var tbody = document.getElementById('suppressionsTableBody');
                tbody.textContent = '';
                if (!data.success) {
                    var errRow = document.createElement('tr');
                    var errCell = document.createElement('td');
                    errCell.colSpan = 6;
                    errCell.className = 'text-danger';
                    errCell.textContent = 'Failed to load';
                    errRow.appendChild(errCell);
                    tbody.appendChild(errRow);
                    return;
                }
                var rows = (data.data && data.data.suppressions) || [];
                if (rows.length === 0) {
                    var noneRow = document.createElement('tr');
                    var noneCell = document.createElement('td');
                    noneCell.colSpan = 6;
                    noneCell.className = 'text-muted';
                    noneCell.textContent = 'None.';
                    noneRow.appendChild(noneCell);
                    tbody.appendChild(noneRow);
                    return;
                }
                rows.forEach(function(s) {
                    var tr = document.createElement('tr');
                    tr.appendChild(makeCell(s.primary_domain || 'Server-wide'));
                    var issueCell = document.createElement('td');
                    if (s.issue_title) {
                        issueCell.textContent = s.issue_title;
                        if (s.issue_category) {
                            var cat = document.createElement('small');
                            cat.className = 'text-muted d-block';
                            cat.textContent = s.issue_category;
                            issueCell.appendChild(cat);
                        }
                    } else {
                        var code = document.createElement('code');
                        code.title = 'signature';
                        code.textContent = (s.signature || '').slice(0, 12) + '…';
                        issueCell.appendChild(code);
                    }
                    tr.appendChild(issueCell);
                    tr.appendChild(makeCell(s.reason || ''));
                    tr.appendChild(makeCell(s.suppressed_at));
                    tr.appendChild(makeCell(String(s.suppressed_occurrence_count || 0)));
                    var actionCell = document.createElement('td');
                    var unsuppBtn = document.createElement('button');
                    unsuppBtn.className = 'btn btn-xs btn-outline-danger btn-unsuppress';
                    unsuppBtn.setAttribute('data-id', parseInt(s.id, 10));
                    unsuppBtn.textContent = 'Un-suppress';
                    actionCell.appendChild(unsuppBtn);
                    tr.appendChild(actionCell);
                    tbody.appendChild(tr);
                });
            });
    }

    suppBody.addEventListener('shown.bs.collapse', loadSuppressions);

    document.addEventListener('click', function(e) {
        var btn = e.target.closest('.btn-unsuppress');
        if (!btn) return;
        var sid = parseInt(btn.getAttribute('data-id'), 10);
        if (!sid) return;
        if (!confirm('Un-suppress this signature? Future matching events will alert you again.')) return;
        fetch('api/ai-monitor-customer.php?action=un_suppress', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken },
            body: JSON.stringify({ suppression_id: sid })
        }).then(function(r) { return r.json(); }).then(function(data) {
            if (!data.success) { alert('Failed: ' + (data.error || 'unknown')); return; }
            loadSuppressions();
        });
    });
})();
</script>
