"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SystemCollector = void 0;
const si = __importStar(require("systeminformation"));
const logger_1 = require("../logger");
const location_1 = require("../services/location");
let prevNetworkStats = null;
class SystemCollector {
    constructor() {
        this.collecting = false;
        this.lastTunnelTelemetry = null;
        this.lastTunnelTelemetryAt = 0;
        this.hardwareProfileCache = null;
        this.hardwareProfileFetchedAt = 0;
        this.hardwareProfileTtlMs = 15 * 60 * 1000;
        this.locationCollector = new location_1.AgentLocationCollector();
    }
    async collect() {
        if (this.collecting) {
            logger_1.logger.debug('System collection already in progress, skipping');
            throw new Error('Collection in progress');
        }
        this.collecting = true;
        try {
            const collectorWarnings = [];
            const logCollectorError = (name, err) => {
                const msg = `${name}: ${err?.message || String(err)}`;
                logger_1.logger.warn(`System collector '${name}' failed: ${err?.message || String(err)}`);
                collectorWarnings.push(msg);
            };
            const [cpuLoad, cpuSpeed, cpuTemp, mem, disk, network, gpuData, battery, osInfo] = await Promise.all([
                si.currentLoad().catch((e) => { logCollectorError('currentLoad', e); return null; }),
                si.cpuCurrentSpeed().catch((e) => { logCollectorError('cpuCurrentSpeed', e); return null; }),
                si.cpuTemperature().catch((e) => { logCollectorError('cpuTemperature', e); return null; }),
                si.mem().catch((e) => { logCollectorError('mem', e); return null; }),
                si.fsSize().catch((e) => { logCollectorError('fsSize', e); return []; }),
                si.networkStats().catch((e) => { logCollectorError('networkStats', e); return []; }),
                si.graphics().catch((e) => { logCollectorError('graphics', e); return null; }),
                si.battery().catch((e) => { logCollectorError('battery', e); return null; }),
                si.osInfo().catch((e) => { logCollectorError('osInfo', e); return null; }),
            ]);
            const hardware = await this.collectHardwareProfile().catch((e) => { logCollectorError('hardwareProfile', e); return undefined; });
            const location = await this.locationCollector.collect();
            const locationStatus = this.locationCollector.getStatus();
            // Merge network stats with interface details (IP, MAC, gateway etc.)
            const [networkStats, networkIfaceDetails] = await Promise.all([
                Promise.resolve(network),
                si.networkInterfaces().catch((e) => { logCollectorError('networkInterfaces', e); return []; }),
            ]);
            const defaultGateway = await si.networkGatewayDefault().catch((e) => { logCollectorError('networkGatewayDefault', e); return ''; });
            let dnsServers = [];
            try {
                const dns = require('dns');
                dnsServers = dns.getServers ? dns.getServers() : [];
            }
            catch { }
            const ifaceArr = Array.isArray(networkIfaceDetails) ? networkIfaceDetails : [];
            const statsArr = Array.isArray(networkStats) ? networkStats : [];
            const defaultIface = ifaceArr.find((d) => d.default === true || (d.operstate === 'up' && !d.internal && d.ip4)) || ifaceArr.find((d) => !d.internal && d.ip4) || null;
            const defaultIfaceName = defaultIface?.iface || '';
            const detailByName = new Map();
            ifaceArr.forEach((d) => {
                const key = String(d?.iface || d?.ifaceName || '').trim();
                if (key)
                    detailByName.set(key, d);
            });
            const statsByName = new Map();
            statsArr.forEach((s) => {
                const key = String(s?.iface || s?.ifaceName || '').trim();
                if (key)
                    statsByName.set(key, s);
            });
            const interfaceNames = Array.from(new Set([
                ...Array.from(detailByName.keys()),
                ...Array.from(statsByName.keys()),
            ]));
            const vpnHintRegex = /\b(vpn|wireguard|wg\d*|wintun|openvpn|ovpn|tun\d*|tap\d*|utun\d*|ppp\d*|ipsec|ikev2|nordlynx|protonvpn|tailscale|zerotier|zscaler|z[-\s]?tunnel|zpa|zia|globalprotect|pan[-\s]?gp|anyconnect|forticlient|fortinet|pulse\s*secure|checkpoint|hamachi|meshnet|warp|cloudflare\s*warp|netskope|prisma)\b/i;
            // Build a default-gateway map per iface (si.networkGatewayDefault for primary, no per-iface API)
            const networkInfo = interfaceNames.map((ifaceName) => {
                const iface = statsByName.get(ifaceName) || {};
                const detail = detailByName.get(ifaceName) || {};
                const descriptor = [
                    ifaceName,
                    detail.ifaceName,
                    detail.iface,
                    detail.type,
                    detail.vendor,
                    detail.model,
                    detail.driver,
                    detail.mac,
                    detail.dnsSuffix,
                ]
                    .filter(Boolean)
                    .join(' ')
                    .toLowerCase();
                const isWg = /\b(wireguard|wg\d*|wintun)\b/i.test(descriptor);
                const isTun = /\b(tun\d*|tap\d*|utun\d*|openvpn|ppp|ovpn)\b/i.test(descriptor);
                const isIpsec = /\b(ipsec|ikev2|isakmp|l2tp)\b/i.test(descriptor);
                const isGre = /\b(gre|gretap|ip_gre)\b/i.test(descriptor);
                const isVpnName = vpnHintRegex.test(descriptor);
                const isVpn = isWg || isTun || isIpsec || isGre || isVpnName;
                const isWifi = /(wi-?fi|wlan\d*|wireless|airport|802\.11)/i.test(descriptor) || String(detail.type || '').toLowerCase() === 'wireless';
                const isLo = /(^|[\s_-])(lo\d*|loopback)([\s_-]|$)/i.test(descriptor) || detail.internal === true;
                let type = 'ethernet';
                let vpnType;
                if (isLo) {
                    type = 'loopback';
                }
                else if (isWifi && !isVpn) {
                    type = 'wifi';
                }
                else if (isVpn) {
                    type = 'vpn';
                    vpnType = isWg ? 'wireguard' : isIpsec ? 'ipsec' : isTun ? 'openvpn' : 'other';
                }
                else if (isWifi) {
                    type = 'wifi';
                }
                return {
                    interface: ifaceName,
                    name: ifaceName,
                    rxBytes: iface.rx_bytes || 0,
                    txBytes: iface.tx_bytes || 0,
                    rxSec: iface.rx_sec || 0,
                    txSec: iface.tx_sec || 0,
                    rxPerSec: iface.rx_sec || 0,
                    txPerSec: iface.tx_sec || 0,
                    speed: iface.speed || detail.speed || 0,
                    operstate: iface.operstate || detail.operstate || 'unknown',
                    type,
                    ...(vpnType ? { vpnType } : {}),
                    ip4: detail.ip4 || undefined,
                    ip6: detail.ip6 || undefined,
                    mac: detail.mac || undefined,
                    gateway: detail.gateway || ((defaultGateway && ifaceName === defaultIfaceName) ? String(defaultGateway) : undefined),
                    dnsServers: dnsServers.length ? dnsServers : undefined,
                    mtu: detail.mtu || undefined,
                    isInternal: detail.internal === true || isLo,
                    description: detail.ifaceName || undefined,
                    vendor: detail.vendor || undefined,
                    model: detail.model || undefined,
                    driver: detail.driver || undefined,
                    isVirtual: detail.virtual === true || undefined,
                };
            }).filter((i) => {
                const hasTraffic = Number(i.rxSec || 0) > 0 || Number(i.txSec || 0) > 0;
                const hasIp = !!(i.ip4 || i.ip6);
                const up = String(i.operstate || '').toLowerCase() === 'up';
                return !!i.interface && (hasTraffic || hasIp || up || i.type === 'vpn' || i.type === 'wifi');
            });
            const routes = this.buildInterfaceRoutes(ifaceArr, String(defaultGateway || ''), defaultIfaceName);
            const tunnels = await this.collectTunnelTelemetry(networkInfo);
            const metrics = {
                cpu: {
                    load: cpuLoad?.currentLoad || 0,
                    user: cpuLoad?.currentLoadUser || 0,
                    system: cpuLoad?.currentLoadSystem || 0,
                    idle: cpuLoad?.currentLoadIdle || 0,
                    cores: cpuLoad?.cpus?.map((c) => c.load) || [],
                    speed: cpuSpeed?.avg || 0,
                    temperature: cpuTemp?.main !== null ? cpuTemp?.main : undefined,
                },
                memory: {
                    total: mem?.total || 0,
                    used: mem?.used || 0,
                    free: mem?.free || 0,
                    percent: mem ? (mem.used / mem.total) * 100 : 0,
                    swapTotal: mem?.swaptotal || 0,
                    swapUsed: mem?.swapused || 0,
                    swapPercent: mem?.swaptotal ? (mem.swapused / mem.swaptotal) * 100 : 0,
                },
                disk: disk.map((d) => ({
                    fs: d.fs,
                    type: d.type,
                    size: d.size,
                    used: d.used,
                    available: d.available,
                    percent: d.use || 0,
                    mount: d.mount,
                })),
                network: networkInfo,
                networkInterfaces: networkInfo,
                routes,
                tunnels: tunnels || undefined,
                uptime: process.uptime(),
                loadAvg: (() => { try {
                    return require('os').loadavg();
                }
                catch {
                    return [0, 0, 0];
                } })(),
            };
            // GPU info
            if (gpuData?.controllers?.length) {
                metrics.gpu = gpuData.controllers.map((g) => ({
                    model: g.model,
                    vendor: g.vendor,
                    vram: g.vram || 0,
                    temperature: g.temperatureGpu || undefined,
                    utilizationGpu: g.utilizationGpu || undefined,
                    utilizationMemory: g.utilizationMemory || undefined,
                }));
            }
            // Battery info
            if (battery?.hasBattery) {
                metrics.battery = {
                    hasBattery: true,
                    isCharging: battery.isCharging,
                    percent: battery.percent,
                    timeRemaining: battery.timeRemaining,
                };
            }
            // Package manager detection
            metrics.packageManagers = await this.detectPackageManagers();
            if (hardware)
                metrics.hardware = hardware;
            if (location)
                metrics.location = location;
            if (locationStatus)
                metrics.locationStatus = locationStatus;
            if (collectorWarnings.length > 0)
                metrics.collectorWarnings = collectorWarnings;
            return metrics;
        }
        finally {
            this.collecting = false;
        }
    }
    async collectHardwareProfile(force = false) {
        const now = Date.now();
        if (!force && this.hardwareProfileCache && (now - this.hardwareProfileFetchedAt) < this.hardwareProfileTtlMs) {
            return this.hardwareProfileCache;
        }
        try {
            const [cpuInfo, systemInfo, memLayout, baseboard, bios, memInfo] = await Promise.all([
                si.cpu().catch(() => null),
                si.system().catch(() => null),
                si.memLayout().catch(() => []),
                si.baseboard().catch(() => null),
                si.bios().catch(() => null),
                si.mem().catch(() => null),
            ]);
            const toNumber = (value) => {
                const n = Number(value);
                return Number.isFinite(n) ? n : 0;
            };
            const modulesRaw = Array.isArray(memLayout) ? memLayout : [];
            const modules = modulesRaw.map((module) => {
                const speedMHz = Math.max(toNumber(module?.clockSpeed), toNumber(module?.configuredClockSpeed), toNumber(module?.frequency));
                return {
                    bank: String(module?.bank || module?.bankLocator || module?.slot || '').trim(),
                    sizeBytes: Math.max(0, toNumber(module?.size)),
                    speedMHz,
                    formFactor: String(module?.formFactor || '').trim(),
                    type: String(module?.memoryType || module?.type || '').trim(),
                    manufacturer: String(module?.manufacturer || '').trim(),
                    partNumber: String(module?.partNum || module?.partNumber || '').trim(),
                    serialNumber: String(module?.serialNum || module?.serialNumber || '').trim(),
                };
            });
            const populatedModules = modules.filter((module) => module.sizeBytes > 0).length;
            const sodimmCount = modules.filter((module) => /sodimm/i.test(String(module.formFactor || ''))).length;
            const moduleSpeeds = modules.map((module) => module.speedMHz).filter((speed) => speed > 0);
            const totalFromModules = modules.reduce((sum, module) => sum + Math.max(0, Number(module.sizeBytes || 0)), 0);
            const totalMemoryBytes = Math.max(toNumber(memInfo?.total), totalFromModules);
            const profile = {
                system: {
                    manufacturer: String(systemInfo?.manufacturer || '').trim(),
                    model: String(systemInfo?.model || '').trim(),
                    version: String(systemInfo?.version || '').trim(),
                    serial: String(systemInfo?.serial || '').trim(),
                    sku: String(systemInfo?.sku || '').trim(),
                    uuid: String(systemInfo?.uuid || '').trim(),
                    virtual: systemInfo?.virtual === true || String(systemInfo?.virtual || '').toLowerCase() === 'true',
                },
                cpu: {
                    manufacturer: String(cpuInfo?.manufacturer || '').trim(),
                    brand: String(cpuInfo?.brand || '').trim(),
                    socket: String(cpuInfo?.socket || '').trim(),
                    socketCount: Math.max(1, toNumber(cpuInfo?.processors || 1)),
                    physicalCores: Math.max(0, toNumber(cpuInfo?.physicalCores)),
                    logicalCores: Math.max(0, toNumber(cpuInfo?.cores)),
                    speedGHz: toNumber(cpuInfo?.speed),
                    maxSpeedGHz: toNumber(cpuInfo?.speedMax),
                },
                memory: {
                    totalBytes: totalMemoryBytes,
                    moduleCount: modules.length,
                    populatedModules,
                    sodimmCount,
                    speedMHz: moduleSpeeds.length
                        ? Math.round(moduleSpeeds.reduce((sum, speed) => sum + speed, 0) / moduleSpeeds.length)
                        : 0,
                    maxSpeedMHz: moduleSpeeds.length ? Math.max(...moduleSpeeds) : 0,
                    modules,
                },
                baseboard: {
                    manufacturer: String(baseboard?.manufacturer || '').trim(),
                    model: String(baseboard?.model || '').trim(),
                    version: String(baseboard?.version || '').trim(),
                    serial: String(baseboard?.serial || '').trim(),
                },
                bios: {
                    vendor: String(bios?.vendor || '').trim(),
                    version: String(bios?.version || '').trim(),
                    releaseDate: String(bios?.releaseDate || '').trim(),
                },
            };
            this.hardwareProfileCache = profile;
            this.hardwareProfileFetchedAt = now;
            return profile;
        }
        catch (error) {
            logger_1.logger.debug('Hardware profile collection failed', { error: String(error?.message || error || '') });
            return this.hardwareProfileCache || undefined;
        }
    }
    async collectQuick() {
        const [cpuLoad, mem] = await Promise.all([
            si.currentLoad().catch(() => null),
            si.mem().catch(() => null),
        ]);
        return {
            cpu: {
                load: cpuLoad?.currentLoad || 0,
                user: cpuLoad?.currentLoadUser || 0,
                system: cpuLoad?.currentLoadSystem || 0,
                idle: cpuLoad?.currentLoadIdle || 0,
                cores: cpuLoad?.cpus?.map((c) => c.load) || [],
                speed: 0,
            },
            memory: {
                total: mem?.total || 0,
                used: mem?.used || 0,
                free: mem?.free || 0,
                percent: mem ? (mem.used / mem.total) * 100 : 0,
                swapTotal: 0,
                swapUsed: 0,
                swapPercent: 0,
            },
        };
    }
    async collectWifi() {
        try {
            const [wifiConns, wifiNets, netIfaces, defaultGw] = await Promise.all([
                si.wifiConnections().catch(() => []),
                si.wifiNetworks().catch(() => []),
                si.networkInterfaces().catch(() => []),
                si.networkGatewayDefault().catch(() => ''),
            ]);
            const conn = wifiConns?.[0];
            if (!conn)
                return null;
            // Find matching network for extra info
            const matchNet = wifiNets?.find((n) => n.bssid === conn.bssid || n.ssid === conn.ssid);
            // Find the wifi interface for IP
            const ifaces = Array.isArray(netIfaces) ? netIfaces : [];
            const connIface = String(conn.iface || '').trim().toLowerCase();
            const vpnHintRegex = /\b(vpn|wireguard|wg\d*|wintun|openvpn|ovpn|tun\d*|tap\d*|utun\d*|ppp\d*|ipsec|ikev2|nordlynx|protonvpn|tailscale|zerotier|zscaler|z[-\s]?tunnel|zpa|zia|globalprotect|pan[-\s]?gp|anyconnect|forticlient|fortinet|pulse\s*secure|checkpoint|hamachi|meshnet|warp|cloudflare\s*warp|netskope|prisma)\b/i;
            const matchWireless = (i) => {
                const ifaceName = String(i?.iface || i?.ifaceName || '').toLowerCase();
                const descriptor = [i?.iface, i?.ifaceName, i?.type, i?.vendor, i?.model, i?.driver]
                    .filter(Boolean)
                    .join(' ')
                    .toLowerCase();
                if (!ifaceName || vpnHintRegex.test(descriptor))
                    return false;
                if (connIface && (ifaceName === connIface || ifaceName.includes(connIface) || connIface.includes(ifaceName)))
                    return true;
                const type = String(i?.type || '').toLowerCase();
                if (type === 'wireless' || type === 'wifi')
                    return true;
                return /(wi-?fi|wlan\d*|wireless|airport|802\.11)/i.test(descriptor);
            };
            const wifiIface = ifaces.find((i) => matchWireless(i) && String(i?.ip4 || '').trim())
                || ifaces.find((i) => matchWireless(i));
            // Get DNS servers
            let dnsServers = [];
            try {
                const dns = require('dns');
                dnsServers = dns.getServers ? dns.getServers() : [];
            }
            catch { }
            return {
                ssid: conn.ssid || '',
                bssid: conn.bssid || '',
                rssi: conn.signalLevel ?? matchNet?.signalLevel ?? 0,
                signal_quality: conn.quality ?? matchNet?.quality ?? 0,
                channel: conn.channel ?? matchNet?.channel ?? 0,
                frequency: conn.frequency ?? matchNet?.frequency ?? 0,
                link_speed_mbps: conn.txRate || 0,
                tx_rate_mbps: conn.txRate || 0,
                rx_rate_mbps: conn.rxRate || 0,
                noise: matchNet?.noise || 0,
                security: conn.security || matchNet?.security || '',
                interface_name: conn.iface || wifiIface?.iface || '',
                ip_address: wifiIface?.ip4 || conn.ip4 || '',
                gateway: typeof defaultGw === 'string' ? defaultGw : '',
                dns_servers: dnsServers,
                connection_type: 'wifi',
                phy_link_speed_mbps: wifiIface?.speed || 0,
                errors: 0,
            };
        }
        catch (e) {
            logger_1.logger.debug('WiFi collection failed (may not have wifi adapter)');
            return null;
        }
    }
    async collectIPAddresses() {
        try {
            const ifaces = await si.networkInterfaces().catch(() => []);
            const ifaceArr = Array.isArray(ifaces) ? ifaces : [];
            const ips = [];
            let primary = '';
            for (const iface of ifaceArr) {
                if (iface.ip4 && iface.ip4 !== '127.0.0.1' && !iface.internal) {
                    ips.push(iface.ip4);
                    if (!primary && iface.operstate === 'up')
                        primary = iface.ip4;
                }
                if (iface.ip6 && !iface.ip6.startsWith('fe80') && !iface.internal) {
                    ips.push(iface.ip6);
                }
            }
            if (!primary && ips.length)
                primary = ips[0];
            return { primary, all: ips };
        }
        catch {
            return { primary: '', all: [] };
        }
    }
    async collectPhysicalNetwork() {
        try {
            const ifaces = await si.networkInterfaces().catch(() => []);
            const ifaceArr = Array.isArray(ifaces) ? ifaces : [];
            const wiredIface = ifaceArr.find((i) => i.operstate === 'up' && !i.internal && i.type !== 'wireless' && i.ip4 && i.ip4 !== '127.0.0.1');
            if (!wiredIface)
                return null;
            return {
                type: 'ethernet',
                speed: wiredIface.speed || 0,
                interface_name: wiredIface.iface || '',
                ip: wiredIface.ip4 || '',
            };
        }
        catch {
            return null;
        }
    }
    createEmptyTunnelSummary() {
        return {
            tls: 0,
            dtls: 0,
            ipsec: 0,
            gre: 0,
            wireguard: 0,
            openvpn: 0,
            other: 0,
            total: 0,
        };
    }
    resolveTunnelProvider(descriptor) {
        const text = String(descriptor || '').toLowerCase();
        if (!text)
            return undefined;
        if (/\b(zscaler|z[-\s]?tunnel|zpa|zia|zsatunnel)\b/i.test(text))
            return 'zscaler';
        if (/\bnetskope\b/i.test(text))
            return 'netskope';
        if (/\b(globalprotect|pan[-\s]?gp|prisma)\b/i.test(text))
            return 'prisma';
        if (/\b(anyconnect|cisco secure client)\b/i.test(text))
            return 'anyconnect';
        if (/\b(forticlient|fortinet)\b/i.test(text))
            return 'fortinet';
        if (/\b(pulse\s*secure|ivanti)\b/i.test(text))
            return 'ivanti';
        if (/\b(cloudflare\s*warp|warp)\b/i.test(text))
            return 'cloudflare-warp';
        if (/\btailscale\b/i.test(text))
            return 'tailscale';
        if (/\bzerotier\b/i.test(text))
            return 'zerotier';
        return undefined;
    }
    classifyInterfaceTunnelKind(descriptor) {
        const text = String(descriptor || '').toLowerCase();
        if (!text)
            return null;
        if (/\b(wireguard|wg\d*|wintun)\b/i.test(text))
            return 'wireguard';
        if (/\b(ipsec|ikev2|isakmp|l2tp)\b/i.test(text))
            return 'ipsec';
        if (/\b(gre|gretap|ip_gre)\b/i.test(text))
            return 'gre';
        if (/\b(openvpn|ovpn|tun\d*|tap\d*|utun\d*|ppp\d*)\b/i.test(text))
            return 'openvpn';
        if (/\b(zscaler|z[-\s]?tunnel|zpa|zia)\b/i.test(text))
            return 'tls';
        if (/\bvpn\b/i.test(text))
            return 'other';
        return null;
    }
    classifyProcessTunnelKind(descriptor) {
        const text = String(descriptor || '').toLowerCase();
        if (!text)
            return null;
        if (/\b(zscaler|z[-\s]?tunnel|zpa|zia|zsatunnel)\b/i.test(text))
            return 'tls';
        if (/\b(wireguard|wg\d*|wintun)\b/i.test(text))
            return 'wireguard';
        if (/\b(ipsec|ikev2|isakmp|l2tp)\b/i.test(text))
            return 'ipsec';
        if (/\b(gre|gretap|ip_gre)\b/i.test(text))
            return 'gre';
        if (/\b(openvpn|ovpn|tap|tun)\b/i.test(text))
            return 'openvpn';
        if (/\b(vpn|tunnel|anyconnect|globalprotect|forticlient|netskope|prisma|warp|tailscale|zerotier)\b/i.test(text))
            return 'other';
        return null;
    }
    summarizeTunnelKind(summary, kind) {
        if (kind === 'tls')
            summary.tls += 1;
        else if (kind === 'dtls')
            summary.dtls += 1;
        else if (kind === 'ipsec')
            summary.ipsec += 1;
        else if (kind === 'gre')
            summary.gre += 1;
        else if (kind === 'wireguard')
            summary.wireguard += 1;
        else if (kind === 'openvpn')
            summary.openvpn += 1;
        else
            summary.other += 1;
    }
    async collectTunnelTelemetry(networkInfo) {
        const now = Date.now();
        if (this.lastTunnelTelemetry && (now - this.lastTunnelTelemetryAt) < 60000) {
            return this.lastTunnelTelemetry;
        }
        const summary = this.createEmptyTunnelSummary();
        const providers = new Set();
        const detections = [];
        const seen = new Set();
        const addDetection = (detection) => {
            const key = [
                detection.kind,
                detection.source,
                detection.interface || '',
                detection.process || '',
                detection.protocol || '',
                detection.localAddress || '',
                detection.peerAddress || '',
                String(detection.peerPort || ''),
                detection.provider || '',
            ].join('|').toLowerCase();
            if (seen.has(key))
                return;
            seen.add(key);
            detections.push(detection);
            this.summarizeTunnelKind(summary, detection.kind);
            if (detection.provider)
                providers.add(detection.provider);
        };
        for (const iface of networkInfo || []) {
            const ifaceName = String(iface.interface || iface.name || '').trim();
            if (!ifaceName)
                continue;
            const isLoopback = /^(lo\d*|loopback)/i.test(ifaceName);
            if (isLoopback)
                continue;
            const descriptor = [
                ifaceName,
                iface.description,
                iface.vendor,
                iface.model,
                iface.driver,
                iface.type,
                iface.vpnType,
            ].filter(Boolean).join(' ');
            const kind = this.classifyInterfaceTunnelKind(descriptor);
            if (!kind)
                continue;
            const state = String(iface.operstate || '').trim().toLowerCase();
            const isUp = state === 'up' || state === 'connected';
            const hasAddress = !!String(iface.ip4 || '').trim() || !!String(iface.ip6 || '').trim();
            const hasTraffic = Number(iface.rxSec || 0) > 0 ||
                Number(iface.txSec || 0) > 0 ||
                Number(iface.rxBytes || 0) > 0 ||
                Number(iface.txBytes || 0) > 0;
            if (!isUp && !hasAddress && !hasTraffic)
                continue;
            const provider = this.resolveTunnelProvider(descriptor);
            addDetection({
                kind,
                source: 'interface',
                confidence: kind === 'other' ? 'low' : 'medium',
                interface: ifaceName,
                provider,
                note: `Interface signature matched ${kind.toUpperCase()} tunnel patterns (${isUp ? 'active' : 'inactive'}).`,
            });
        }
        const ifaceByAddress = new Map();
        for (const iface of networkInfo || []) {
            const ifaceName = String(iface.interface || iface.name || '').trim();
            if (!ifaceName)
                continue;
            const ip4 = String(iface.ip4 || '').trim();
            const ip6 = String(iface.ip6 || '').trim();
            if (ip4)
                ifaceByAddress.set(ip4, ifaceName);
            if (ip6)
                ifaceByAddress.set(ip6, ifaceName);
        }
        try {
            const connections = await si.networkConnections().catch(() => []);
            const tunnelProcessRegex = /\b(zscaler|z[-\s]?tunnel|zpa|zia|vpn|tunnel|ipsec|ikev2|isakmp|wireguard|openvpn|ovpn|anyconnect|globalprotect|forticlient|fortinet|netskope|prisma|warp|tailscale|zerotier)\b/i;
            for (const conn of (Array.isArray(connections) ? connections : [])) {
                if (detections.length >= 32)
                    break;
                const protocol = String(conn?.protocol || '').trim().toLowerCase();
                if (!protocol)
                    continue;
                const processName = String(conn?.process || conn?.processName || '').trim();
                const localAddress = String(conn?.localAddress || '').trim();
                const peerAddress = String(conn?.peerAddress || '').trim();
                const localPort = Math.max(0, Math.floor(Number(conn?.localPort || 0) || 0));
                const peerPort = Math.max(0, Math.floor(Number(conn?.peerPort || 0) || 0));
                const descriptor = `${processName} ${localAddress} ${peerAddress} ${protocol}`;
                const provider = this.resolveTunnelProvider(descriptor);
                const tunnelProcessHint = tunnelProcessRegex.test(descriptor);
                const ifaceName = localAddress ? (ifaceByAddress.get(localAddress) || '') : '';
                const isTcp = protocol.includes('tcp');
                const isUdp = protocol.includes('udp');
                if (isTcp && peerPort === 443 && (provider === 'zscaler' || tunnelProcessHint)) {
                    addDetection({
                        kind: 'tls',
                        source: 'connection',
                        confidence: provider === 'zscaler' ? 'high' : 'medium',
                        interface: ifaceName || undefined,
                        process: processName || undefined,
                        protocol,
                        localAddress: localAddress || undefined,
                        peerAddress: peerAddress || undefined,
                        peerPort,
                        provider,
                        note: 'Secure TCP tunnel signature detected on port 443.',
                    });
                }
                if (isUdp && peerPort === 443 && (provider === 'zscaler' || tunnelProcessHint)) {
                    addDetection({
                        kind: 'dtls',
                        source: 'connection',
                        confidence: provider === 'zscaler' ? 'high' : 'medium',
                        interface: ifaceName || undefined,
                        process: processName || undefined,
                        protocol,
                        localAddress: localAddress || undefined,
                        peerAddress: peerAddress || undefined,
                        peerPort,
                        provider,
                        note: 'DTLS/UDP tunnel signature detected on port 443.',
                    });
                }
                const isIpsecPort = peerPort === 500 || peerPort === 4500 || localPort === 500 || localPort === 4500;
                if (isUdp && isIpsecPort && (tunnelProcessHint || /\b(ipsec|ikev2|isakmp)\b/i.test(descriptor))) {
                    addDetection({
                        kind: 'ipsec',
                        source: 'connection',
                        confidence: 'high',
                        interface: ifaceName || undefined,
                        process: processName || undefined,
                        protocol,
                        localAddress: localAddress || undefined,
                        peerAddress: peerAddress || undefined,
                        peerPort: peerPort || localPort || undefined,
                        provider,
                        note: 'IPsec/IKE tunnel signature detected on UDP 500/4500.',
                    });
                }
                const protocolIsGre = protocol === 'gre' || protocol === '47' || /\bgre\b/i.test(protocol);
                if (protocolIsGre || /\b(gre|gretap|ip_gre)\b/i.test(descriptor)) {
                    addDetection({
                        kind: 'gre',
                        source: 'connection',
                        confidence: 'high',
                        interface: ifaceName || undefined,
                        process: processName || undefined,
                        protocol,
                        localAddress: localAddress || undefined,
                        peerAddress: peerAddress || undefined,
                        peerPort: peerPort || undefined,
                        provider,
                        note: 'GRE tunnel signature detected.',
                    });
                }
            }
        }
        catch {
            // Ignore connection sampling failures; interface signatures are still reported.
        }
        try {
            const processData = await si.processes().catch(() => null);
            const processList = Array.isArray(processData?.list) ? processData.list : [];
            for (const proc of processList) {
                if (detections.length >= 32)
                    break;
                const processName = String(proc?.name || '').trim();
                if (!processName)
                    continue;
                const descriptor = `${processName} ${String(proc?.path || '').trim()} ${String(proc?.command || '').trim()}`;
                const kind = this.classifyProcessTunnelKind(descriptor);
                if (!kind)
                    continue;
                const provider = this.resolveTunnelProvider(descriptor);
                addDetection({
                    kind,
                    source: 'process',
                    confidence: provider ? 'high' : (kind === 'other' ? 'low' : 'medium'),
                    process: processName,
                    provider,
                    note: `Tunnel process signature detected (${processName}).`,
                });
            }
        }
        catch {
            // Ignore process sampling failures.
        }
        summary.total = summary.tls + summary.dtls + summary.ipsec + summary.gre + summary.wireguard + summary.openvpn + summary.other;
        const telemetry = {
            generatedAt: new Date().toISOString(),
            sampleWindowSeconds: 60,
            providers: Array.from(providers.values()).sort(),
            summary,
            detections: detections.slice(0, 24),
        };
        this.lastTunnelTelemetry = telemetry;
        this.lastTunnelTelemetryAt = now;
        return telemetry;
    }
    buildInterfaceRoutes(ifaces, defaultGateway, defaultIfaceName) {
        const routes = [];
        if (defaultGateway) {
            routes.push({
                destination: '0.0.0.0/0',
                gateway: defaultGateway,
                interface: defaultIfaceName || '',
                metric: 1,
                family: 'ipv4',
                isDefault: true,
            });
        }
        for (const iface of ifaces || []) {
            if (!iface || iface.internal)
                continue;
            const name = String(iface.iface || '');
            if (iface.ip4) {
                const cidr = Number(iface.ip4subnet || 24);
                const network = this.networkFromIPv4(String(iface.ip4), cidr);
                routes.push({
                    destination: `${network}/${cidr}`,
                    gateway: 'on-link',
                    interface: name,
                    metric: Number(iface.metric || 0),
                    family: 'ipv4',
                });
            }
            if (iface.ip6) {
                const cidr6 = Number(iface.ip6subnet || 64);
                routes.push({
                    destination: `${iface.ip6}/${cidr6}`,
                    gateway: 'on-link',
                    interface: name,
                    metric: Number(iface.metric || 0),
                    family: 'ipv6',
                });
            }
        }
        const dedup = new Map();
        for (const r of routes) {
            const key = `${r.destination}|${r.gateway}|${r.interface}|${r.family}`;
            if (!dedup.has(key))
                dedup.set(key, r);
        }
        return Array.from(dedup.values()).slice(0, 64);
    }
    networkFromIPv4(ip, cidr) {
        const parts = ip.split('.').map((n) => Number(n));
        if (parts.length !== 4 || parts.some((n) => Number.isNaN(n)))
            return ip;
        const bits = Math.max(0, Math.min(32, cidr));
        const ipNum = ((parts[0] << 24) >>> 0) + ((parts[1] << 16) >>> 0) + ((parts[2] << 8) >>> 0) + (parts[3] >>> 0);
        const mask = bits === 0 ? 0 : ((0xffffffff << (32 - bits)) >>> 0);
        const net = ipNum & mask;
        return [24, 16, 8, 0].map((shift) => (net >>> shift) & 255).join('.');
    }
    async detectPackageManagers() {
        const { execSync } = require('child_process');
        const managers = [];
        const checks = [];
        if (process.platform === 'win32') {
            checks.push({ name: 'winget', cmd: 'winget', versionFlag: '--version' }, { name: 'choco', cmd: 'choco', versionFlag: '--version' }, { name: 'npm', cmd: 'npm', versionFlag: '--version' }, { name: 'python', cmd: 'python', versionFlag: '--version' }, { name: 'pip', cmd: 'pip', versionFlag: '--version' });
        }
        else if (process.platform === 'darwin') {
            checks.push({ name: 'brew', cmd: 'brew', versionFlag: '--version' }, { name: 'npm', cmd: 'npm', versionFlag: '--version' }, { name: 'pip', cmd: 'pip', versionFlag: '--version' }, { name: 'pip3', cmd: 'pip3', versionFlag: '--version' });
        }
        else {
            checks.push({ name: 'apt', cmd: 'apt', versionFlag: '--version' }, { name: 'apt-get', cmd: 'apt-get', versionFlag: '--version' }, { name: 'yum', cmd: 'yum', versionFlag: '--version' }, { name: 'dnf', cmd: 'dnf', versionFlag: '--version' }, { name: 'pacman', cmd: 'pacman', versionFlag: '--version' }, { name: 'zypper', cmd: 'zypper', versionFlag: '--version' }, { name: 'npm', cmd: 'npm', versionFlag: '--version' }, { name: 'pip', cmd: 'pip', versionFlag: '--version' }, { name: 'pip3', cmd: 'pip3', versionFlag: '--version' });
        }
        for (const check of checks) {
            try {
                const cmd = process.platform === 'win32'
                    ? (check.name === 'winget'
                        ? `powershell -NoProfile -Command "$cmd = Get-Command winget.exe -ErrorAction SilentlyContinue; if($cmd){ Write-Output $cmd.Source; & $cmd.Source ${check.versionFlag || '--version'}; exit 0 }; $fallback = Join-Path $env:LOCALAPPDATA 'Microsoft\\WindowsApps\\winget.exe'; if(Test-Path $fallback){ Write-Output $fallback; & $fallback ${check.versionFlag || '--version'}; exit 0 }; $pkg = Get-AppxPackage Microsoft.DesktopAppInstaller -ErrorAction SilentlyContinue | Sort-Object Version -Descending | Select-Object -First 1; if($pkg){ $cli = Join-Path $pkg.InstallLocation 'AppInstallerCLI.exe'; if(Test-Path $cli){ Write-Output $cli; & $cli ${check.versionFlag || '--version'}; exit 0 } }; exit 1"`
                        : `cmd /c where ${check.cmd} >nul 2>&1 && ${check.cmd} ${check.versionFlag || ''}`)
                    : `which ${check.cmd} && ${check.cmd} ${check.versionFlag || ''}`;
                const output = execSync(cmd, {
                    encoding: 'utf-8',
                    timeout: 5000,
                    stdio: ['pipe', 'pipe', 'pipe']
                }).trim();
                const lines = output.split('\n');
                const version = lines[lines.length - 1]?.trim() || '';
                const path = lines[0]?.trim() || '';
                managers.push({
                    name: check.name,
                    available: true,
                    version: version || undefined,
                    path: path || undefined,
                });
            }
            catch (e) {
                managers.push({
                    name: check.name,
                    available: false,
                });
            }
        }
        return managers;
    }
}
exports.SystemCollector = SystemCollector;
//# sourceMappingURL=system.js.map