/* ═══════════════════════════════════════════════════════════════════════════
   Trade360 Monitor — Phase 1C Dashboard (fully generic / config-driven)

   Everything is driven by two data sources:
     1. GET /api/metrics/config  → thresholds, server names, site configs
     2. SignalR ReceiveSnapshot  → live metric values

   Add a server, site, or adapter to appsettings.json and the dashboard
   picks it up automatically. No names, thresholds, or labels are hardcoded.
   ═══════════════════════════════════════════════════════════════════════════ */

const { useState, useEffect, useRef, useCallback, useMemo } = React;
const {
    AreaChart, Area, ResponsiveContainer
} = Recharts;


// ─── Utility Helpers ────────────────────────────────────────────────────────

function fmt(v, decimals = 1) {
    if (v === null || v === undefined || isNaN(v)) return '—';
    return Number(v).toFixed(decimals);
}

function fmtInt(v) {
    if (v === null || v === undefined || isNaN(v)) return '—';
    return Math.round(Number(v)).toLocaleString();
}

function fmtMem(mb) {
    if (mb === null || mb === undefined) return '—';
    if (mb >= 1024) return (mb / 1024).toFixed(1) + ' GB';
    return Math.round(mb) + ' MB';
}

function fmtTime(ts) {
    if (!ts) return '';
    return new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
}

function fmtDuration(seconds) {
    if (!seconds && seconds !== 0) return '';
    if (seconds < 60) return Math.round(seconds) + 's';
    if (seconds < 3600) return Math.floor(seconds / 60) + 'm ' + Math.round(seconds % 60) + 's';
    return Math.floor(seconds / 3600) + 'h ' + Math.floor((seconds % 3600) / 60) + 'm';
}

function agoSeconds(isoTs) {
    if (!isoTs) return 0;
    return (Date.now() - new Date(isoTs).getTime()) / 1000;
}


// ─── Threshold Helpers (config-driven) ──────────────────────────────────────

/**
 * Look up a threshold from config for a given category path.
 * Returns { warnAbove, warnBelow, critAbove, critBelow } or empty object.
 */
function getThreshold(config, category, metricName, target) {
    if (!config || !config.thresholds) return {};
    const t = config.thresholds;

    // Site metrics: check overrides first, then defaults
    if (category === 'site') {
        if (target && t.siteOverrides && t.siteOverrides[target] && t.siteOverrides[target][metricName]) {
            return t.siteOverrides[target][metricName];
        }
        if (t.siteDefaults && t.siteDefaults[metricName]) {
            return t.siteDefaults[metricName];
        }
        return {};
    }

    // Worker process / pool metrics: check overrides first
    if (category === 'pool') {
        if (target && t.workerProcessOverrides && t.workerProcessOverrides[target]) {
            const ov = t.workerProcessOverrides[target];
            if (ov[metricName]) return ov[metricName];
        }
        if (t.workerProcess && t.workerProcess[metricName]) {
            return t.workerProcess[metricName];
        }
        return {};
    }

    // Connection pool
    if (category === 'connpool') {
        if (t.connectionPool && t.connectionPool[metricName]) return t.connectionPool[metricName];
        return {};
    }

    // SQL
    if (category === 'sql') {
        // Per-app overrides
        if (target && target !== 'global' && t.sqlAppOverrides && t.sqlAppOverrides[target]) {
            return t.sqlAppOverrides[target]; // these are flat {warnAbove, critAbove}
        }
        if (t.sql && t.sql[metricName]) return t.sql[metricName];
        return {};
    }

    // Server hardware
    if (category === 'server') {
        if (t.server && t.server[metricName]) return t.server[metricName];
        return {};
    }

    // Network
    if (category === 'network') {
        if (t.network && t.network[metricName]) return t.network[metricName];
        return {};
    }

    // IIS global
    if (category === 'iis') {
        if (t.iisGlobal && t.iisGlobal[metricName]) return t.iisGlobal[metricName];
        return {};
    }

    return {};
}

/** Evaluate severity. Returns 'ok', 'warn', or 'crit'. */
function severity(value, thresh) {
    if (value === null || value === undefined || isNaN(value) || !thresh) return 'ok';
    if (thresh.critAbove != null && value > thresh.critAbove) return 'crit';
    if (thresh.critBelow != null && value < thresh.critBelow) return 'crit';
    if (thresh.warnAbove != null && value > thresh.warnAbove) return 'warn';
    if (thresh.warnBelow != null && value < thresh.warnBelow) return 'warn';
    return 'ok';
}

function sevColor(sev) {
    if (sev === 'crit') return 'var(--red)';
    if (sev === 'warn') return 'var(--amber)';
    return 'var(--green)';
}

/** Worst severity in an array */
function worstSev(sevArr) {
    if (sevArr.includes('crit')) return 'crit';
    if (sevArr.includes('warn')) return 'warn';
    return 'ok';
}

function sevLabel(sev) {
    if (sev === 'crit') return 'CRITICAL';
    if (sev === 'warn') return 'WARNING';
    return 'HEALTHY';
}


// ─── Inline Styles ──────────────────────────────────────────────────────────

const S = {
    app: { maxWidth: 1440, margin: '0 auto', padding: '0 20px 40px' },

    header: {
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '16px 0', borderBottom: '1px solid var(--border)',
        marginBottom: 20, flexWrap: 'wrap', gap: 12,
    },
    headerLeft: { display: 'flex', alignItems: 'center', gap: 14 },
    logo: {
        fontFamily: 'var(--font-mono)', fontSize: '1.15rem',
        fontWeight: 700, color: 'var(--text-heading)', letterSpacing: '-0.03em',
    },
    logoAccent: { color: 'var(--blue)' },
    headerRight: {
        display: 'flex', alignItems: 'center', gap: 16,
        fontSize: '0.8rem', color: 'var(--text-secondary)',
    },
    connBadge: (on) => ({
        display: 'inline-flex', alignItems: 'center', gap: 6,
        padding: '3px 10px', borderRadius: 4, fontSize: '0.75rem',
        fontWeight: 600, fontFamily: 'var(--font-mono)',
        background: on ? 'var(--green-bg)' : 'var(--red-bg)',
        color: on ? 'var(--green)' : 'var(--red)',
        border: '1px solid ' + (on ? 'var(--green-border)' : 'var(--red-border)'),
    }),
    connDot: (on) => ({
        width: 6, height: 6, borderRadius: '50%',
        background: on ? 'var(--green)' : 'var(--red)',
        animation: on ? 'none' : 'pulse 1.5s infinite',
    }),

    grid: (minW) => ({
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fit, minmax(' + minW + 'px, 1fr))',
        gap: 16, marginBottom: 16,
    }),
    fullWidth: { marginBottom: 16 },

    card: {
        background: 'var(--bg-card)', border: '1px solid var(--border)',
        borderRadius: 'var(--radius-lg)', padding: 16, animation: 'fadeIn 0.3s ease-out',
    },
    cardTitle: {
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        marginBottom: 12, paddingBottom: 8, borderBottom: '1px solid var(--border)',
    },
    cardTitleText: {
        fontSize: '0.78rem', fontWeight: 600, fontFamily: 'var(--font-mono)',
        color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.06em',
    },
    badge: (sev) => ({
        fontSize: '0.7rem', fontWeight: 600, fontFamily: 'var(--font-mono)',
        padding: '2px 8px', borderRadius: 3,
        background: sev === 'crit' ? 'var(--red-bg)' : sev === 'warn' ? 'var(--amber-bg)' : 'var(--green-bg)',
        color: sevColor(sev),
        border: '1px solid ' + (sev === 'crit' ? 'var(--red-border)' : sev === 'warn' ? 'var(--amber-border)' : 'var(--green-border)'),
    }),

    metricRow: {
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '5px 0', fontSize: '0.85rem',
    },
    metricLabel: { color: 'var(--text-secondary)', fontWeight: 400 },
    metricVal: (sev) => ({
        fontFamily: 'var(--font-mono)', fontWeight: 600, fontSize: '0.85rem',
        color: sevColor(sev), fontVariantNumeric: 'tabular-nums',
    }),

    tbl: { width: '100%', borderCollapse: 'separate', borderSpacing: 0, fontSize: '0.82rem' },
    th: {
        fontFamily: 'var(--font-mono)', fontSize: '0.7rem', fontWeight: 600,
        color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em',
        textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid var(--border)',
    },
    td: { padding: '7px 8px', borderBottom: '1px solid var(--border)', verticalAlign: 'middle' },
    tdVal: (sev) => ({
        fontFamily: 'var(--font-mono)', fontWeight: 600, fontVariantNumeric: 'tabular-nums',
        color: sevColor(sev), padding: '7px 8px', borderBottom: '1px solid var(--border)', textAlign: 'right',
    }),
    tdSpark: { padding: '4px 0', borderBottom: '1px solid var(--border)', width: 180 },

    alertItem: (sev) => ({
        display: 'flex', alignItems: 'flex-start', gap: 10,
        padding: '8px 12px', margin: '4px 0', borderRadius: 'var(--radius)',
        background: sev === 'critical' ? 'var(--red-bg)' : 'var(--amber-bg)',
        borderLeft: '3px solid ' + (sev === 'critical' ? 'var(--red)' : 'var(--amber)'),
        fontSize: '0.82rem',
    }),

    noData: { color: 'var(--text-muted)', fontStyle: 'italic', fontSize: '0.82rem', padding: '12px 0' },

    poolBadge: (state) => ({
        display: 'inline-block', fontFamily: 'var(--font-mono)', fontWeight: 600,
        fontSize: '0.68rem', padding: '1px 6px', borderRadius: 3,
        background: state === 'Started' ? 'var(--green-bg)' : 'var(--red-bg)',
        color: state === 'Started' ? 'var(--green)' : 'var(--red)',
        border: '1px solid ' + (state === 'Started' ? 'var(--green-border)' : 'var(--red-border)'),
    }),

    subLabel: {
        fontSize: '0.7rem', fontFamily: 'var(--font-mono)', color: 'var(--text-muted)',
        textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 6,
    },
};


// ─── Sparkline Component ────────────────────────────────────────────────────

function Sparkline({ data, width, height, thresh }) {
    width = width || 160;
    height = height || 32;

    if (!data || data.length < 2) {
        return React.createElement('div', {
            style: { width, height, display: 'flex', alignItems: 'center',
                     color: 'var(--text-muted)', fontSize: '0.7rem' }
        }, 'collecting...');
    }

    var chartData = data.map(function(p) {
        return { t: new Date(p.timestamp).getTime(), v: p.value };
    });

    var latest = chartData[chartData.length - 1].v;
    var sev = severity(latest, thresh);
    var color = sev === 'crit' ? 'var(--red)' : sev === 'warn' ? 'var(--amber)' : 'var(--spark-blue)';

    // Use a unique gradient ID based on color to avoid collisions
    var gradId = 'sg-' + color.replace(/[^a-zA-Z0-9]/g, '');

    return React.createElement(ResponsiveContainer, { width: width, height: height },
        React.createElement(AreaChart, { data: chartData, margin: { top: 2, right: 2, bottom: 2, left: 2 } },
            React.createElement('defs', null,
                React.createElement('linearGradient', { id: gradId, x1: 0, y1: 0, x2: 0, y2: 1 },
                    React.createElement('stop', { offset: '0%', stopColor: color, stopOpacity: 0.25 }),
                    React.createElement('stop', { offset: '100%', stopColor: color, stopOpacity: 0 })
                )
            ),
            React.createElement(Area, {
                type: 'monotone', dataKey: 'v', stroke: color, strokeWidth: 1.5,
                fill: 'url(#' + gradId + ')', dot: false, isAnimationActive: false,
            })
        )
    );
}


// ─── MetricRow with sparkline ───────────────────────────────────────────────

function MetricRow(props) {
    var label = props.label, value = props.value, sev = props.sev;
    var sparkData = props.sparkData, thresh = props.thresh;

    return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, padding: '5px 0' } },
        React.createElement('span', { style: { ...S.metricLabel, flex: 1, minWidth: 100, fontSize: '0.85rem' } }, label),
        React.createElement('div', { style: { flex: '0 0 130px' } },
            React.createElement(Sparkline, { data: sparkData, width: 130, height: 26, thresh: thresh })
        ),
        React.createElement('span', { style: { ...S.metricVal(sev), minWidth: 80, textAlign: 'right' } }, value)
    );
}


// ─── Server Health Card (generic for any server) ────────────────────────────

function ServerHealthCard(props) {
    var title = props.title, metrics = props.metrics, serverKey = props.serverKey;
    var sparklines = props.sparklines, config = props.config;

    if (!metrics) {
        return React.createElement('div', { style: S.card },
            React.createElement('div', { style: S.cardTitle },
                React.createElement('span', { style: S.cardTitleText }, title),
                React.createElement('span', { style: S.badge('ok') }, 'OFFLINE')
            ),
            React.createElement('div', { style: S.noData }, 'Waiting for data...')
        );
    }

    // Build metric list dynamically from whatever the server metrics object contains
    var rows = [];
    var sevs = [];

    function addRow(label, value, displayVal, metricName, sparkSuffix) {
        var t = getThreshold(config, 'server', metricName);
        var s = severity(value, t);
        sevs.push(s);
        var sk = serverKey + ':server:global:' + sparkSuffix;
        rows.push(React.createElement(MetricRow, {
            key: metricName, label: label, value: displayVal, sev: s,
            sparkData: sparklines[sk], thresh: t,
        }));
    }

    addRow('CPU', metrics.cpuPercent, fmt(metrics.cpuPercent) + '%', 'CpuPercent', 'CpuPercent');
    addRow('Available RAM', metrics.availableRamMb, fmtMem(metrics.availableRamMb), 'AvailableRamMb', 'AvailableRamMb');
    addRow('RAM Committed', metrics.ramCommittedPercent, fmt(metrics.ramCommittedPercent) + '%', 'RamCommittedPercent', 'RamCommittedPercent');
    addRow('Disk Queue', metrics.diskQueueLength, fmt(metrics.diskQueueLength), 'DiskQueueLength', 'DiskQueueLength');
    addRow('GC Time', metrics.gcTimePercent, fmt(metrics.gcTimePercent) + '%', 'GcTimePercent', 'GcTimePercent');

    // Disks (dynamic — whatever drives the server has)
    if (metrics.disks) {
        metrics.disks.forEach(function(d) {
            var t = getThreshold(config, 'server', 'DiskFreeGb');
            var s = severity(d.freeGb, t);
            sevs.push(s);
            var sk = serverKey + ':server:' + d.driveLetter + ':DiskFreeGb';
            rows.push(React.createElement(MetricRow, {
                key: 'disk-' + d.driveLetter, label: 'Disk ' + d.driveLetter,
                value: fmt(d.freeGb) + ' GB free', sev: s,
                sparkData: sparklines[sk], thresh: t,
            }));
        });
    }

    var overall = worstSev(sevs);

    return React.createElement('div', { style: S.card },
        React.createElement('div', { style: S.cardTitle },
            React.createElement('span', { style: S.cardTitleText }, title),
            React.createElement('span', { style: S.badge(overall) }, sevLabel(overall))
        ),
        rows
    );
}


// ─── IIS Overview (generic — reads all sites from snapshot) ─────────────────

function IisOverview(props) {
    var snapshot = props.snapshot, sparklines = props.sparklines, config = props.config;
    var iis = snapshot ? snapshot.iisGlobal : null;
    var sites = snapshot ? (snapshot.sites || []) : [];

    // IIS global thresholds from config
    var qT = getThreshold(config, 'iis', 'RequestsQueued');
    var cT = getThreshold(config, 'iis', 'RequestsCurrent');
    var wT = getThreshold(config, 'iis', 'RequestWaitTimeMs');
    var qS = iis ? severity(iis.requestsQueued, qT) : 'ok';
    var cS = iis ? severity(iis.requestsCurrent, cT) : 'ok';
    var wS = iis ? severity(iis.requestWaitTimeMs, wT) : 'ok';

    // Sort: production first, then alphabetical
    var sorted = sites.slice().sort(function(a, b) {
        if (a.server !== b.server) return a.server === 'production' ? -1 : 1;
        return a.siteName.localeCompare(b.siteName);
    });

    function GlobalStat(label, value, sev) {
        return React.createElement('span', {
            style: { fontFamily: 'var(--font-mono)', fontSize: '0.75rem',
                     display: 'flex', alignItems: 'center', gap: 6 }
        },
            React.createElement('span', { style: { color: 'var(--text-muted)' } }, label + ':'),
            React.createElement('span', { style: { color: sevColor(sev), fontWeight: 600 } }, value)
        );
    }

    return React.createElement('div', { style: { ...S.card, ...S.fullWidth } },
        React.createElement('div', { style: S.cardTitle },
            React.createElement('span', { style: S.cardTitleText }, 'IIS Overview'),
            React.createElement('div', { style: { display: 'flex', gap: 16 } },
                GlobalStat('Queued', iis ? fmtInt(iis.requestsQueued) : '—', qS),
                GlobalStat('Current', iis ? fmtInt(iis.requestsCurrent) : '—', cS),
                GlobalStat('Wait', iis ? fmtInt(iis.requestWaitTimeMs) + 'ms' : '—', wS)
            )
        ),
        sites.length === 0
            ? React.createElement('div', { style: S.noData }, 'No site data yet.')
            : React.createElement('table', { style: S.tbl },
                React.createElement('thead', null,
                    React.createElement('tr', null,
                        React.createElement('th', { style: { ...S.th, width: 180 } }, 'Site'),
                        React.createElement('th', { style: { ...S.th, textAlign: 'right', width: 60 } }, 'Exec'),
                        React.createElement('th', { style: { ...S.th, textAlign: 'right', width: 80 } }, 'Time ms'),
                        React.createElement('th', { style: { ...S.th, textAlign: 'right', width: 60 } }, 'Err/s'),
                        React.createElement('th', { style: { ...S.th, textAlign: 'right', width: 60 } }, 'Req/s'),
                        React.createElement('th', { style: { ...S.th, width: 180 } }, 'Requests Executing (1h)')
                    )
                ),
                React.createElement('tbody', null,
                    sorted.map(function(site) {
                        var execT = getThreshold(config, 'site', 'RequestsExecuting', site.siteName);
                        var timeT = getThreshold(config, 'site', 'RequestExecTimeMs', site.siteName);
                        var errT  = getThreshold(config, 'site', 'ErrorsPerSec', site.siteName);
                        var execS = severity(site.requestsExecuting, execT);
                        var timeS = severity(site.requestExecTimeMs, timeT);
                        var errS  = severity(site.errorsPerSec, errT);

                        var sparkKey = site.server + ':site:' + site.siteName + ':RequestsExecuting';

                        return React.createElement('tr', {
                            key: site.server + ':' + site.siteName + ':' + site.siteId,
                            onMouseEnter: function(e) { e.currentTarget.style.background = 'var(--bg-hover)'; },
                            onMouseLeave: function(e) { e.currentTarget.style.background = 'transparent'; },
                        },
                            React.createElement('td', { style: S.td },
                                React.createElement('span', { style: { fontWeight: 600, color: 'var(--text-primary)' } },
                                    React.createElement('span', {
                                        style: { display: 'inline-block', width: 7, height: 7, borderRadius: '50%',
                                                 background: sevColor(execS), marginRight: 8 }
                                    }),
                                    site.siteName
                                ),
                                site.server !== 'production' ? React.createElement('span', {
                                    style: { fontSize: '0.65rem', fontFamily: 'var(--font-mono)',
                                             color: 'var(--text-muted)', marginLeft: 6,
                                             background: 'var(--bg-card-alt)', padding: '1px 5px', borderRadius: 3 }
                                }, site.server) : null,
                                React.createElement('div', {
                                    style: { fontSize: '0.7rem', color: 'var(--text-muted)', fontWeight: 400 }
                                }, 'pool: ' + site.appPoolName)
                            ),
                            React.createElement('td', { style: S.tdVal(execS) }, fmtInt(site.requestsExecuting)),
                            React.createElement('td', { style: S.tdVal(timeS) }, fmtInt(site.requestExecTimeMs)),
                            React.createElement('td', { style: S.tdVal(errS) }, fmt(site.errorsPerSec)),
                            React.createElement('td', { style: S.tdVal('ok') }, fmt(site.requestsPerSec)),
                            React.createElement('td', { style: S.tdSpark },
                                React.createElement(Sparkline, {
                                    data: sparklines[sparkKey], width: 170, height: 28, thresh: execT,
                                })
                            )
                        );
                    })
                )
            )
    );
}


// ─── SQL Server Summary (generic) ───────────────────────────────────────────

function SqlSummary(props) {
    var sql = props.sql, sparklines = props.sparklines, config = props.config;

    if (!sql) {
        return React.createElement('div', { style: S.card },
            React.createElement('div', { style: S.cardTitle },
                React.createElement('span', { style: S.cardTitleText }, 'SQL Server')
            ),
            React.createElement('div', { style: S.noData }, 'No SQL data available.')
        );
    }

    var connT = getThreshold(config, 'sql', 'TotalConnections');
    var blockT = getThreshold(config, 'sql', 'BlockedProcesses');
    var waitT = getThreshold(config, 'sql', 'MaxWaitSeconds');
    var connS = severity(sql.totalConnections, connT);
    var blockS = severity(sql.blockedProcessCount, blockT);
    var waitS = severity(sql.maxWaitSeconds, waitT);
    var overall = worstSev([connS, blockS, waitS]);

    // Per-app connection thresholds
    var perAppT = getThreshold(config, 'sql', 'PerAppConnections');

    return React.createElement('div', { style: S.card },
        React.createElement('div', { style: S.cardTitle },
            React.createElement('span', { style: S.cardTitleText }, 'SQL Server'),
            React.createElement('span', { style: S.badge(overall) }, sevLabel(overall))
        ),
        // Main metrics
        React.createElement('div', { style: S.metricRow },
            React.createElement('span', { style: S.metricLabel }, 'Total Connections'),
            React.createElement('div', { style: { flex: '0 0 120px' } },
                React.createElement(Sparkline, {
                    data: sparklines['production:sql:global:TotalConnections'],
                    width: 120, height: 24, thresh: connT,
                })
            ),
            React.createElement('span', { style: { ...S.metricVal(connS), minWidth: 50, textAlign: 'right' } },
                fmtInt(sql.totalConnections))
        ),
        React.createElement('div', { style: S.metricRow },
            React.createElement('span', { style: S.metricLabel }, 'Blocked Processes'),
            React.createElement('div', { style: { flex: '0 0 120px' } },
                React.createElement(Sparkline, {
                    data: sparklines['production:sql:global:BlockedProcesses'],
                    width: 120, height: 24, thresh: blockT,
                })
            ),
            React.createElement('span', { style: { ...S.metricVal(blockS), minWidth: 50, textAlign: 'right' } },
                fmtInt(sql.blockedProcessCount))
        ),
        React.createElement('div', { style: S.metricRow },
            React.createElement('span', { style: S.metricLabel }, 'Max Wait'),
            React.createElement('span', { style: S.metricVal(waitS) }, fmt(sql.maxWaitSeconds) + 's')
        ),

        // Connections by app (dynamic from snapshot data)
        sql.connectionsByApp && sql.connectionsByApp.length > 0 ?
            React.createElement('div', { style: { marginTop: 10, paddingTop: 8, borderTop: '1px solid var(--border)' } },
                React.createElement('div', { style: S.subLabel }, 'Connections by App'),
                sql.connectionsByApp.slice(0, 8).map(function(app) {
                    // Check per-app override first, then general perApp threshold
                    var appT = getThreshold(config, 'sql', 'PerAppConnections', app.programName);
                    if (!appT.warnAbove && !appT.critAbove) appT = perAppT;
                    var appS = severity(app.connectionCount, appT);
                    return React.createElement('div', {
                        key: app.programName || '?', style: { ...S.metricRow, padding: '3px 0' }
                    },
                        React.createElement('span', {
                            style: { ...S.metricLabel, fontSize: '0.78rem', flex: 1,
                                     overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }
                        }, app.programName || '(unknown)'),
                        React.createElement('span', { style: { ...S.metricVal(appS), fontSize: '0.78rem' } },
                            fmtInt(app.connectionCount))
                    );
                })
            ) : null,

        // Blocked processes
        sql.blockedProcesses && sql.blockedProcesses.length > 0 ?
            React.createElement('div', { style: { marginTop: 10, paddingTop: 8, borderTop: '1px solid var(--border)' } },
                React.createElement('div', { style: { ...S.subLabel, color: 'var(--red)' } },
                    'Blocked (' + sql.blockedProcesses.length + ')'),
                sql.blockedProcesses.slice(0, 5).map(function(bp) {
                    return React.createElement('div', {
                        key: bp.sessionId,
                        style: { fontSize: '0.75rem', padding: '4px 8px', marginBottom: 3,
                                 background: 'var(--red-bg)', borderRadius: 'var(--radius)',
                                 borderLeft: '2px solid var(--red)', fontFamily: 'var(--font-mono)' }
                    },
                        React.createElement('div', { style: { color: 'var(--text-primary)' } },
                            'SPID ' + bp.sessionId + ' blocked by ' + bp.blockingSessionId +
                            ' — ' + fmt(bp.waitSeconds) + 's (' + bp.waitType + ')'),
                        bp.queryText ? React.createElement('div', {
                            style: { color: 'var(--text-muted)', fontSize: '0.7rem', marginTop: 2,
                                     overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '100%' }
                        }, bp.queryText.substring(0, 120)) : null
                    );
                })
            ) : null
    );
}


// ─── Network Card (generic — reads adapters from snapshot) ──────────────────

function NetworkCard(props) {
    var network = props.network, sparklines = props.sparklines, config = props.config;

    if (!network || !network.adapters || network.adapters.length === 0) {
        return React.createElement('div', { style: S.card },
            React.createElement('div', { style: S.cardTitle },
                React.createElement('span', { style: S.cardTitleText }, 'Network')
            ),
            React.createElement('div', { style: S.noData }, 'No network data.')
        );
    }

    return React.createElement('div', { style: S.card },
        React.createElement('div', { style: S.cardTitle },
            React.createElement('span', { style: S.cardTitleText }, 'Network')
        ),
        network.adapters.map(function(adapter) {
            var skSent = network.server + ':network:' + adapter.name + ':SentMbps';
            var skRecv = network.server + ':network:' + adapter.name + ':ReceivedMbps';

            return React.createElement('div', { key: adapter.name, style: { marginBottom: 10 } },
                React.createElement('div', {
                    style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                             padding: '6px 0', borderBottom: '1px solid var(--border)' }
                },
                    React.createElement('span', { style: { fontSize: '0.82rem', fontWeight: 500, color: 'var(--text-primary)', flex: 1 } },
                        adapter.name),
                    React.createElement('div', {
                        style: { display: 'flex', gap: 16, fontFamily: 'var(--font-mono)', fontSize: '0.8rem', fontVariantNumeric: 'tabular-nums' }
                    },
                        React.createElement('span', { style: { color: 'var(--cyan)' } }, '\u2191 ' + fmt(adapter.sentMbps, 2) + ' MB/s'),
                        React.createElement('span', { style: { color: 'var(--blue)' } }, '\u2193 ' + fmt(adapter.receivedMbps, 2) + ' MB/s')
                    )
                ),
                React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 4 } },
                    React.createElement('div', { style: { flex: 1 } },
                        React.createElement(Sparkline, { data: sparklines[skSent], width: '100%', height: 28, thresh: {} })
                    ),
                    React.createElement('div', { style: { flex: 1 } },
                        React.createElement(Sparkline, { data: sparklines[skRecv], width: '100%', height: 28, thresh: {} })
                    )
                )
            );
        })
    );
}


// ─── Alerts Panel (generic) ─────────────────────────────────────────────────

function AlertsPanel(props) {
    var alerts = props.alerts || [];
    var history = props.alertHistory || [];
    var countCrit = alerts.filter(function(a) { return a.severity === 'critical'; }).length;
    var overallSev = countCrit > 0 ? 'crit' : alerts.length > 0 ? 'warn' : 'ok';
    var label = alerts.length === 0 ? 'ALL CLEAR' : alerts.length + ' ACTIVE';

    return React.createElement('div', { style: S.card },
        React.createElement('div', { style: S.cardTitle },
            React.createElement('span', { style: S.cardTitleText }, 'Active Alerts (' + alerts.length + ')'),
            React.createElement('span', { style: S.badge(overallSev) }, label)
        ),
        alerts.length === 0
            ? React.createElement('div', {
                style: { display: 'flex', alignItems: 'center', gap: 8,
                         color: 'var(--green)', fontSize: '0.85rem', padding: '8px 0' }
            }, '\u2713 All systems operating normally')
            : alerts.map(function(alert, i) {
                return React.createElement('div', { key: alert.id || i, style: S.alertItem(alert.severity) },
                    React.createElement('span', {
                        style: { fontFamily: 'var(--font-mono)', fontWeight: 700, fontSize: '0.68rem',
                                 color: alert.severity === 'critical' ? 'var(--red)' : 'var(--amber)', flexShrink: 0, paddingTop: 1 }
                    }, alert.severity === 'critical' ? 'CRIT' : 'WARN'),
                    React.createElement('span', { style: { color: 'var(--text-primary)', flex: 1 } }, alert.message),
                    React.createElement('div', { style: { display: 'flex', flexDirection: 'column', alignItems: 'flex-end' } },
                        React.createElement('span', { style: { fontSize: '0.72rem', fontFamily: 'var(--font-mono)', color: 'var(--text-muted)' } },
                            fmtTime(alert.firedAt)),
                        React.createElement('span', { style: { fontSize: '0.66rem', fontFamily: 'var(--font-mono)', color: 'var(--text-muted)' } },
                            fmtDuration(agoSeconds(alert.firedAt)) + ' ago')
                    )
                );
            }),

        // Recently resolved
        history.length > 0 ? React.createElement('div', null,
            React.createElement('div', {
                style: { ...S.subLabel, marginTop: 14, paddingTop: 8, borderTop: '1px solid var(--border)' }
            }, 'Recently Resolved'),
            history.slice(0, 5).map(function(entry, i) {
                var dur = entry.resolvedAt ? ((new Date(entry.resolvedAt) - new Date(entry.firedAt)) / 1000) : 0;
                return React.createElement('div', {
                    key: entry.id || i,
                    style: { display: 'flex', alignItems: 'center', gap: 8,
                             padding: '4px 0', fontSize: '0.78rem', color: 'var(--text-secondary)',
                             borderBottom: i < Math.min(history.length, 5) - 1 ? '1px solid var(--border)' : 'none' }
                },
                    React.createElement('span', {
                        style: { fontFamily: 'var(--font-mono)', fontSize: '0.68rem', color: 'var(--text-muted)', width: 60 }
                    }, fmtTime(entry.firedAt)),
                    React.createElement('span', {
                        style: { fontFamily: 'var(--font-mono)', fontSize: '0.65rem', width: 36,
                                 color: entry.severity === 'critical' ? 'var(--red-dim)' : 'var(--amber-dim)' }
                    }, entry.severity === 'critical' ? 'CRIT' : 'WARN'),
                    React.createElement('span', {
                        style: { flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }
                    }, entry.message),
                    entry.resolvedAt ? React.createElement('span', {
                        style: { fontFamily: 'var(--font-mono)', fontSize: '0.68rem', color: 'var(--text-muted)' }
                    }, fmtDuration(dur)) : null
                );
            })
        ) : null
    );
}


// ─── App Pools Card (generic) ───────────────────────────────────────────────

function AppPoolsCard(props) {
    var pools = props.pools || [];
    var sparklines = props.sparklines, config = props.config;

    if (pools.length === 0) {
        return React.createElement('div', { style: S.card },
            React.createElement('div', { style: S.cardTitle },
                React.createElement('span', { style: S.cardTitleText }, 'App Pools')
            ),
            React.createElement('div', { style: S.noData }, 'No pool data.')
        );
    }

    var sorted = pools.slice().sort(function(a, b) { return a.poolName.localeCompare(b.poolName); });
    var stoppedCount = sorted.filter(function(p) { return p.state !== 'Started'; }).length;

    return React.createElement('div', { style: S.card },
        React.createElement('div', { style: S.cardTitle },
            React.createElement('span', { style: S.cardTitleText }, 'App Pools (' + pools.length + ')'),
            stoppedCount > 0 ? React.createElement('span', { style: S.badge('crit') }, stoppedCount + ' STOPPED') : null
        ),
        React.createElement('table', { style: S.tbl },
            React.createElement('thead', null,
                React.createElement('tr', null,
                    React.createElement('th', { style: S.th }, 'Pool'),
                    React.createElement('th', { style: { ...S.th, textAlign: 'center', width: 70 } }, 'State'),
                    React.createElement('th', { style: { ...S.th, textAlign: 'right', width: 70 } }, 'RAM'),
                    React.createElement('th', { style: { ...S.th, textAlign: 'right', width: 60 } }, 'CPU %'),
                    React.createElement('th', { style: { ...S.th, textAlign: 'right', width: 70 } }, 'Threads'),
                    React.createElement('th', { style: { ...S.th, width: 140 } }, 'RAM (1h)')
                )
            ),
            React.createElement('tbody', null,
                sorted.map(function(pool) {
                    var totalRam = pool.workers ? pool.workers.reduce(function(s, w) { return s + (w.ramMb || 0); }, 0) : 0;
                    var maxCpu = pool.workers && pool.workers.length > 0
                        ? Math.max.apply(null, pool.workers.map(function(w) { return w.cpuPercent || 0; })) : 0;
                    var totalThreads = pool.workers ? pool.workers.reduce(function(s, w) { return s + (w.threadCount || 0); }, 0) : 0;

                    var ramT = getThreshold(config, 'pool', 'RamMb', pool.poolName);
                    var cpuT = getThreshold(config, 'pool', 'CpuPercent', pool.poolName);
                    var thrT = getThreshold(config, 'pool', 'ThreadCount', pool.poolName);
                    var ramS = severity(totalRam, ramT);
                    var cpuS = severity(maxCpu, cpuT);
                    var thrS = severity(totalThreads, thrT);

                    var sparkKey = (pool.server || 'production') + ':pool:' + pool.poolName + ':RamMb';

                    return React.createElement('tr', {
                        key: pool.poolName,
                        onMouseEnter: function(e) { e.currentTarget.style.background = 'var(--bg-hover)'; },
                        onMouseLeave: function(e) { e.currentTarget.style.background = 'transparent'; },
                    },
                        React.createElement('td', { style: S.td },
                            React.createElement('span', { style: { fontWeight: 600, color: 'var(--text-primary)' } }, pool.poolName),
                            React.createElement('div', { style: { fontSize: '0.68rem', color: 'var(--text-muted)' } },
                                (pool.workerCount || (pool.workers ? pool.workers.length : 0)) + ' worker(s)')
                        ),
                        React.createElement('td', { style: { ...S.td, textAlign: 'center' } },
                            React.createElement('span', { style: S.poolBadge(pool.state) }, pool.state || 'Unknown')),
                        React.createElement('td', { style: S.tdVal(ramS) }, fmtMem(totalRam)),
                        React.createElement('td', { style: S.tdVal(cpuS) }, fmt(maxCpu)),
                        React.createElement('td', { style: S.tdVal(thrS) }, fmtInt(totalThreads)),
                        React.createElement('td', { style: S.tdSpark },
                            React.createElement(Sparkline, { data: sparklines[sparkKey], width: 130, height: 26, thresh: ramT })
                        )
                    );
                })
            )
        )
    );
}


// ─── Connection Pools Card (generic) ────────────────────────────────────────

function ConnectionPoolCard(props) {
    var connPools = props.connPools || [];
    var config = props.config;

    if (connPools.length === 0) {
        return React.createElement('div', { style: S.card },
            React.createElement('div', { style: S.cardTitle },
                React.createElement('span', { style: S.cardTitleText }, 'ADO.NET Connection Pools')
            ),
            React.createElement('div', { style: S.noData }, 'No connection pool data.')
        );
    }

    return React.createElement('div', { style: S.card },
        React.createElement('div', { style: S.cardTitle },
            React.createElement('span', { style: S.cardTitleText }, 'ADO.NET Connection Pools (' + connPools.length + ')')
        ),
        React.createElement('table', { style: S.tbl },
            React.createElement('thead', null,
                React.createElement('tr', null,
                    React.createElement('th', { style: S.th }, 'Pool'),
                    React.createElement('th', { style: { ...S.th, textAlign: 'right', width: 65 } }, 'Pooled'),
                    React.createElement('th', { style: { ...S.th, textAlign: 'right', width: 65 } }, 'Active'),
                    React.createElement('th', { style: { ...S.th, textAlign: 'right', width: 55 } }, 'Free'),
                    React.createElement('th', { style: { ...S.th, textAlign: 'right', width: 65 } }, 'Stalled')
                )
            ),
            React.createElement('tbody', null,
                connPools.map(function(cp) {
                    var pT = getThreshold(config, 'connpool', 'PooledConnections');
                    var aT = getThreshold(config, 'connpool', 'ActiveConnections');
                    var fT = getThreshold(config, 'connpool', 'FreeConnections');
                    var sT = getThreshold(config, 'connpool', 'StalledConnections');

                    return React.createElement('tr', {
                        key: cp.instance || cp.label,
                        onMouseEnter: function(e) { e.currentTarget.style.background = 'var(--bg-hover)'; },
                        onMouseLeave: function(e) { e.currentTarget.style.background = 'transparent'; },
                    },
                        React.createElement('td', {
                            style: { ...S.td, fontWeight: 500, maxWidth: 200,
                                     overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }
                        }, cp.label || cp.instance),
                        React.createElement('td', { style: S.tdVal(severity(cp.pooledConnections, pT)) }, fmtInt(cp.pooledConnections)),
                        React.createElement('td', { style: S.tdVal(severity(cp.activeConnections, aT)) }, fmtInt(cp.activeConnections)),
                        React.createElement('td', { style: S.tdVal(severity(cp.freeConnections, fT)) }, fmtInt(cp.freeConnections)),
                        React.createElement('td', { style: S.tdVal(severity(cp.stalledConnections, sT)) }, fmtInt(cp.stalledConnections))
                    );
                })
            )
        )
    );
}


// ─── Custom Hooks ───────────────────────────────────────────────────────────

function useSignalR(hubUrl) {
    var connectedState = useState(false);
    var connected = connectedState[0], setConnected = connectedState[1];
    var snapshotState = useState(null);
    var snapshot = snapshotState[0], setSnapshot = snapshotState[1];
    var alertsState = useState([]);
    var alerts = alertsState[0], setAlerts = alertsState[1];

    useEffect(function() {
        var connection = new signalR.HubConnectionBuilder()
            .withUrl(hubUrl)
            .withAutomaticReconnect([0, 2000, 5000, 10000, 30000])
            .configureLogging(signalR.LogLevel.Warning)
            .build();

        connection.on('ReceiveSnapshot', function(snap) { setSnapshot(snap); });
        connection.on('ReceiveAlerts', function(list) { setAlerts(list || []); });
        connection.onreconnecting(function() { setConnected(false); });
        connection.onreconnected(function() { setConnected(true); });
        connection.onclose(function() { setConnected(false); });

        function start() {
            connection.start()
                .then(function() { setConnected(true); })
                .catch(function(err) {
                    console.error('SignalR connection failed:', err);
                    setConnected(false);
                    setTimeout(start, 5000);
                });
        }
        start();

        return function() { connection.stop(); };
    }, [hubUrl]);

    return { connected: connected, snapshot: snapshot, alerts: alerts };
}

/** Fetch monitoring config once on mount */
function useConfig() {
    var state = useState(null);
    var config = state[0], setConfig = state[1];

    useEffect(function() {
        fetch('/api/metrics/config')
            .then(function(r) { return r.json(); })
            .then(function(data) { setConfig(data); })
            .catch(function(err) { console.error('Failed to fetch config:', err); });
    }, []);

    return config;
}

/** Fetch sparklines periodically — uses server names from config to build prefixes */
function useSparklines(connected, config) {
    var state = useState({});
    var sparklines = state[0], setSparklines = state[1];

    useEffect(function() {
        if (!connected || !config || !config.servers) return;

        // Build list of server name prefixes from config
        var serverNames = Object.keys(config.servers).map(function(k) {
            return config.servers[k].name;
        });

        function doFetch() {
            // Fetch sparklines for each server prefix in parallel
            var fetches = serverNames.map(function(name) {
                return fetch('/api/metrics/sparklines?prefix=' + encodeURIComponent(name + ':') + '&minutes=60')
                    .then(function(r) { return r.ok ? r.json() : []; })
                    .catch(function() { return []; });
            });

            Promise.all(fetches).then(function(results) {
                var map = {};
                results.forEach(function(data) {
                    if (!Array.isArray(data)) return;
                    data.forEach(function(series) {
                        if (series.key && series.dataPoints) {
                            map[series.key] = series.dataPoints;
                        }
                    });
                });
                setSparklines(map);
            });
        }

        doFetch();
        var interval = setInterval(doFetch, 30000);
        return function() { clearInterval(interval); };
    }, [connected, config]);

    return sparklines;
}

/** Fetch alert history periodically */
function useAlertHistory(connected) {
    var state = useState([]);
    var history = state[0], setHistory = state[1];

    useEffect(function() {
        if (!connected) return;
        function doFetch() {
            fetch('/api/metrics/alerts/history?count=10')
                .then(function(r) { return r.json(); })
                .then(function(data) { setHistory(data || []); })
                .catch(function(err) { console.error('Failed to fetch alert history:', err); });
        }
        doFetch();
        var interval = setInterval(doFetch, 30000);
        return function() { clearInterval(interval); };
    }, [connected]);

    return history;
}


// ─── Main App ───────────────────────────────────────────────────────────────

function App() {
    var hub = useSignalR('/hubs/metrics');
    var config = useConfig();
    var sparklines = useSparklines(hub.connected, config);
    var alertHistory = useAlertHistory(hub.connected);
    var snapshot = hub.snapshot;

    // Track snapshot count
    var countRef = useRef(0);
    if (snapshot) countRef.current++;

    // Build server cards dynamically from config
    var serverCards = [];
    if (config && config.servers) {
        Object.keys(config.servers).forEach(function(key) {
            var srv = config.servers[key];
            // Match server metrics from snapshot
            var metrics = null;
            if (snapshot) {
                // The snapshot has 'production' and 'backup' top-level properties
                // Match by server name
                if (srv.name === 'production') metrics = snapshot.production;
                else if (srv.name === 'backup') metrics = snapshot.backup;
            }
            serverCards.push(React.createElement(ServerHealthCard, {
                key: srv.name,
                title: srv.displayName || srv.name,
                serverKey: srv.name,
                metrics: metrics,
                sparklines: sparklines,
                config: config,
            }));
        });
    }

    return React.createElement('div', { style: S.app },
        // Header
        React.createElement('header', { style: S.header },
            React.createElement('div', { style: S.headerLeft },
                React.createElement('div', { style: S.logo },
                    React.createElement('span', { style: S.logoAccent }, 'TRADE360'),
                    ' MONITOR'
                )
            ),
            React.createElement('div', { style: S.headerRight },
                snapshot ? React.createElement('span', null,
                    'Snapshot #' + countRef.current + ' \u00B7 ' + snapshot.collectionDurationMs + 'ms') : null,
                snapshot ? React.createElement('span', null, fmtTime(snapshot.timestamp)) : null,
                React.createElement('div', { style: S.connBadge(hub.connected) },
                    React.createElement('div', { style: S.connDot(hub.connected) }),
                    hub.connected ? 'LIVE' : 'DISCONNECTED'
                )
            )
        ),

        // Loading state
        !snapshot ? React.createElement('div', {
            style: { display: 'flex', flexDirection: 'column', alignItems: 'center',
                     justifyContent: 'center', padding: '80px 0', gap: 16 }
        },
            React.createElement('div', {
                style: { width: 32, height: 32, border: '3px solid var(--border)',
                         borderTopColor: 'var(--blue)', borderRadius: '50%', animation: 'spin 0.8s linear infinite' }
            }),
            React.createElement('span', { style: { color: 'var(--text-secondary)', fontSize: '0.9rem' } },
                hub.connected ? 'Connected \u2014 waiting for first snapshot...' : 'Connecting to server...')
        ) : null,

        // Dashboard content (only when we have data)
        snapshot ? React.createElement(React.Fragment, null,
            // Server health cards (dynamic from config)
            React.createElement('div', { style: S.grid(380) }, serverCards),

            // IIS Overview
            React.createElement(IisOverview, { snapshot: snapshot, sparklines: sparklines, config: config }),

            // SQL + Alerts + Network
            React.createElement('div', { style: S.grid(300) },
                React.createElement(SqlSummary, { sql: snapshot.sql, sparklines: sparklines, config: config }),
                React.createElement(AlertsPanel, { alerts: hub.alerts, alertHistory: alertHistory }),
                React.createElement(NetworkCard, { network: snapshot.network, sparklines: sparklines, config: config })
            ),

            // App Pools + Connection Pools
            React.createElement('div', { style: S.grid(380) },
                React.createElement(AppPoolsCard, { pools: snapshot.pools, sparklines: sparklines, config: config }),
                React.createElement(ConnectionPoolCard, { connPools: snapshot.connectionPools, config: config })
            )
        ) : null
    );
}


// ─── Mount ──────────────────────────────────────────────────────────────────

var root = ReactDOM.createRoot(document.getElementById('root'));
root.render(React.createElement(App));
