"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.NetworkInterfaceCollector = void 0;
const child_process_1 = require("child_process");
const util_1 = require("util");
const os = __importStar(require("os"));
const execAsync = (0, util_1.promisify)(child_process_1.exec);
function detectInterfaceType(name, flags = '') {
    const n = name.toLowerCase();
    if (n === 'lo' || n.startsWith('loopback'))
        return 'loopback';
    if (n.startsWith('wlan') || n.startsWith('wifi') || n.startsWith('wlp') || n.startsWith('ath') || n.startsWith('wl'))
        return 'wifi';
    if (n.startsWith('wg') || n.startsWith('ppp'))
        return 'vpn';
    if (n.startsWith('tun') || n.startsWith('tap'))
        return 'tunnel';
    if (n.startsWith('br') || n.startsWith('virbr'))
        return 'bridge';
    if (n.startsWith('bond'))
        return 'bond';
    if (n.startsWith('vlan') || n.includes('.'))
        return 'other';
    if (n.startsWith('eth') || n.startsWith('en') || n.startsWith('em') || n.startsWith('eno') || n.startsWith('enp'))
        return 'ethernet';
    if (n.startsWith('docker') || n.startsWith('veth'))
        return 'bridge';
    if (flags.includes('POINTOPOINT'))
        return 'tunnel';
    return 'other';
}
function rssiToQuality(rssi) {
    if (rssi >= -50)
        return 100;
    if (rssi <= -100)
        return 0;
    return Math.round((rssi + 100) * 2);
}
async function pingGateway(gateway, timeout = 2) {
    try {
        const platform = os.platform();
        let cmd;
        if (platform === 'win32') {
            cmd = `powershell -NonInteractive -Command "(Test-Connection -ComputerName ${gateway} -Count 1 -ErrorAction SilentlyContinue).ResponseTime" 2>nul`;
        }
        else {
            cmd = `ping -c1 -W${timeout} -q ${gateway} 2>/dev/null | grep 'rtt\\|avg'`;
        }
        const { stdout } = await execAsync(cmd, { timeout: (timeout + 1) * 1000 });
        if (platform === 'win32') {
            const ms = parseFloat(stdout.trim());
            return isNaN(ms) ? null : ms;
        }
        else {
            // rtt min/avg/max/mdev = 1.234/2.345/3.456/0.123 ms
            const m = stdout.match(/[\d.]+\/([\d.]+)\//);
            return m ? parseFloat(m[1]) : null;
        }
    }
    catch {
        return null;
    }
}
async function collectLinuxInterfaces() {
    const interfaces = new Map();
    // 1. ip link show
    try {
        const { stdout } = await execAsync('ip -json link show 2>/dev/null || ip link show');
        try {
            const links = JSON.parse(stdout);
            for (const link of links) {
                const name = link.ifname;
                const flags = link.flags || [];
                const iface = {
                    interfaceName: name,
                    interfaceType: detectInterfaceType(name, flags.join(' ')),
                    macAddress: link.address && link.address !== '00:00:00:00:00:00' ? link.address : undefined,
                    ipAddresses: [],
                    status: flags.includes('UP') ? 'up' : 'down',
                    mtu: link.mtu,
                };
                if (link.linkinfo?.info_kind === 'bridge')
                    iface.interfaceType = 'bridge';
                if (link.linkinfo?.info_kind === 'bond')
                    iface.interfaceType = 'bond';
                interfaces.set(name, iface);
            }
        }
        catch {
            const lines = stdout.split('\n');
            for (const line of lines) {
                const m = line.match(/^\d+:\s+(\S+):\s+<([^>]*)>/);
                if (m) {
                    const name = m[1].replace('@.*', '');
                    const flags = m[2];
                    interfaces.set(name, {
                        interfaceName: name,
                        interfaceType: detectInterfaceType(name, flags),
                        ipAddresses: [],
                        status: flags.includes('UP') ? 'up' : 'down',
                    });
                }
                const macM = line.match(/link\/ether\s+([\da-f:]+)/i);
                if (macM) {
                    const last = [...interfaces.values()].pop();
                    if (last)
                        last.macAddress = macM[1];
                }
            }
        }
    }
    catch { /* skip */ }
    // 2. ip -json addr show
    try {
        const { stdout } = await execAsync('ip -json addr show 2>/dev/null');
        const addrs = JSON.parse(stdout);
        for (const a of addrs) {
            const iface = interfaces.get(a.ifname);
            if (!iface)
                continue;
            iface.ipAddresses = (a.addr_info || []).map((ai) => `${ai.local}/${ai.prefixlen}`);
        }
    }
    catch { /* skip */ }
    // 3. /proc/net/dev — rx/tx stats
    try {
        const { stdout } = await execAsync('cat /proc/net/dev');
        for (const line of stdout.split('\n').slice(2)) {
            const parts = line.trim().split(/\s+/);
            if (parts.length < 17)
                continue;
            const name = parts[0].replace(':', '');
            const iface = interfaces.get(name);
            if (!iface)
                continue;
            iface.rxBytes = parseInt(parts[1]) || 0;
            iface.rxPackets = parseInt(parts[2]) || 0;
            iface.rxErrors = parseInt(parts[3]) || 0;
            iface.rxDropped = parseInt(parts[4]) || 0;
            iface.txBytes = parseInt(parts[9]) || 0;
            iface.txPackets = parseInt(parts[10]) || 0;
            iface.txErrors = parseInt(parts[11]) || 0;
            iface.txDropped = parseInt(parts[12]) || 0;
        }
    }
    catch { /* skip */ }
    // 4. Link speed from sysfs
    for (const [name, iface] of interfaces) {
        if (iface.interfaceType !== 'ethernet')
            continue;
        try {
            const { stdout } = await execAsync(`cat /sys/class/net/${name}/speed 2>/dev/null`);
            const speed = parseInt(stdout.trim());
            if (speed > 0)
                iface.speedMbps = speed;
        }
        catch { /* skip */ }
    }
    // 5. WiFi info via iw
    for (const [name, iface] of interfaces) {
        if (iface.interfaceType !== 'wifi')
            continue;
        try {
            const { stdout } = await execAsync(`iw dev ${name} link 2>/dev/null`);
            if (stdout.includes('Not connected')) {
                iface.ssid = undefined;
                continue;
            }
            const ssidM = stdout.match(/SSID:\s*(.+)/);
            if (ssidM)
                iface.ssid = ssidM[1].trim();
            const bssidM = stdout.match(/Connected to\s+([\da-fA-F:]+)/);
            if (bssidM)
                iface.bssid = bssidM[1];
            const freqM = stdout.match(/freq:\s*(\d+)/);
            if (freqM) {
                iface.frequencyMhz = parseInt(freqM[1]);
                iface.wifiBand = iface.frequencyMhz >= 5000 ? '5GHz' : iface.frequencyMhz >= 2400 ? '2.4GHz' : undefined;
            }
            const signalM = stdout.match(/signal:\s*(-?\d+)\s*dBm/);
            if (signalM) {
                iface.rssiDbm = parseInt(signalM[1]);
                iface.signalQuality = rssiToQuality(iface.rssiDbm);
            }
            const txM = stdout.match(/tx bitrate:\s*([\d.]+)/);
            if (txM)
                iface.txRateMbps = parseFloat(txM[1]);
            const rxM = stdout.match(/rx bitrate:\s*([\d.]+)/);
            if (rxM)
                iface.rxRateMbps = parseFloat(rxM[1]);
        }
        catch { /* skip */ }
        if (iface.rssiDbm === undefined) {
            try {
                const { stdout } = await execAsync(`iwconfig ${name} 2>/dev/null`);
                const ssidM = stdout.match(/ESSID:"([^"]+)"/);
                if (ssidM)
                    iface.ssid = ssidM[1];
                const sigM = stdout.match(/Signal level=(-?\d+)\s*dBm/);
                if (sigM) {
                    iface.rssiDbm = parseInt(sigM[1]);
                    iface.signalQuality = rssiToQuality(iface.rssiDbm);
                }
                const freqM = stdout.match(/Frequency:([\d.]+)/);
                if (freqM) {
                    iface.frequencyMhz = Math.round(parseFloat(freqM[1]) * 1000);
                    iface.wifiBand = iface.frequencyMhz >= 5000 ? '5GHz' : '2.4GHz';
                }
                const noiseM = stdout.match(/Noise level=(-?\d+)\s*dBm/);
                if (noiseM)
                    iface.noiseDbm = parseInt(noiseM[1]);
            }
            catch { /* skip */ }
        }
    }
    // 6. WireGuard VPN info
    for (const [name, iface] of interfaces) {
        if (!name.startsWith('wg'))
            continue;
        iface.interfaceType = 'vpn';
        iface.vpnType = 'wireguard';
        try {
            const { stdout } = await execAsync(`wg show ${name} 2>/dev/null`);
            const endpointM = stdout.match(/endpoint:\s*(\S+)/);
            if (endpointM)
                iface.vpnEndpoint = endpointM[1].replace(/:\d+$/, '');
            const handshakeM = stdout.match(/latest handshake:\s*(.+)/);
            if (handshakeM) {
                const hs = handshakeM[1].trim();
                iface.vpnLastHandshake = hs;
                iface.vpnConnected = !hs.includes('Never') && !hs.includes('never');
            }
            else {
                iface.vpnConnected = iface.status === 'up';
            }
        }
        catch {
            iface.vpnConnected = iface.status === 'up';
        }
    }
    // 7. OpenVPN / generic tunnel detection
    for (const [name, iface] of interfaces) {
        if (name.startsWith('tun') || name.startsWith('tap')) {
            iface.vpnType = 'openvpn';
            iface.vpnConnected = iface.status === 'up';
        }
    }
    // 8. Default route / gateway
    let defaultGateway = null;
    try {
        const { stdout } = await execAsync('ip route show default 2>/dev/null');
        for (const line of stdout.split('\n')) {
            const m = line.match(/default via (\S+) dev (\S+)/);
            if (m) {
                const iface = interfaces.get(m[2]);
                if (iface) {
                    iface.gateway = m[1];
                    iface.isDefaultRoute = true;
                    defaultGateway = m[1];
                }
            }
        }
    }
    catch { /* skip */ }
    // 9. DNS servers
    try {
        const { stdout } = await execAsync('cat /etc/resolv.conf 2>/dev/null');
        const dns = stdout.match(/^nameserver\s+(\S+)/gm)?.map(l => l.split(/\s+/)[1]) || [];
        for (const iface of interfaces.values()) {
            if (iface.isDefaultRoute && dns.length)
                iface.dnsServers = dns;
        }
    }
    catch { /* skip */ }
    // 10. Gateway latency ping
    if (defaultGateway) {
        const latency = await pingGateway(defaultGateway);
        for (const iface of interfaces.values()) {
            if (iface.isDefaultRoute && latency !== null)
                iface.latencyMs = latency;
        }
    }
    return Array.from(interfaces.values()).filter(i => i.interfaceType !== 'loopback' &&
        !i.interfaceName.startsWith('docker') &&
        !i.interfaceName.startsWith('veth'));
}
async function collectWindowsInterfaces() {
    const interfaces = [];
    try {
        const ps = `Get-NetAdapter | Select-Object Name,InterfaceDescription,Status,MacAddress,LinkSpeed,MtuSize | ConvertTo-Json`;
        const { stdout } = await execAsync(`powershell -NonInteractive -Command "${ps}" 2>nul`);
        const adapters = JSON.parse(stdout);
        const adapterList = Array.isArray(adapters) ? adapters : [adapters];
        for (const a of adapterList) {
            const name = a.Name || '';
            const desc = (a.InterfaceDescription || '').toLowerCase();
            const rawStatus = (a.Status || '').toLowerCase();
            let ifType = 'ethernet';
            if (desc.includes('wi-fi') || desc.includes('wireless') || desc.includes('802.11'))
                ifType = 'wifi';
            else if (desc.includes('vpn') || desc.includes('tunnel') || desc.includes('wireguard') || desc.includes('openvpn') || desc.includes('tap'))
                ifType = 'vpn';
            else if (desc.includes('loopback'))
                ifType = 'loopback';
            else if (desc.includes('bluetooth'))
                ifType = 'other';
            // Map Windows adapter statuses to up/down/unknown
            let status = 'unknown';
            if (rawStatus === 'up')
                status = 'up';
            else if (rawStatus === 'down' || rawStatus === 'disconnected' || rawStatus === 'notpresent' ||
                rawStatus === 'lowerlayerdown' || rawStatus === 'dormant' || rawStatus.includes('disconnect')) {
                status = 'down';
            }
            const iface = {
                interfaceName: name,
                interfaceType: ifType,
                macAddress: a.MacAddress?.replace(/-/g, ':'),
                ipAddresses: [],
                status,
                speedMbps: a.LinkSpeed ? Math.round(a.LinkSpeed / 1000000) : undefined,
                mtu: a.MtuSize,
            };
            interfaces.push(iface);
        }
    }
    catch { /* PowerShell unavailable */ }
    // IP addresses
    try {
        const ps2 = `Get-NetIPAddress | Select-Object InterfaceAlias,IPAddress,PrefixLength | ConvertTo-Json`;
        const { stdout } = await execAsync(`powershell -NonInteractive -Command "${ps2}" 2>nul`);
        const ips = JSON.parse(stdout);
        const ipList = Array.isArray(ips) ? ips : [ips];
        for (const ip of ipList) {
            const iface = interfaces.find(i => i.interfaceName === ip.InterfaceAlias);
            if (iface)
                iface.ipAddresses.push(`${ip.IPAddress}/${ip.PrefixLength}`);
        }
    }
    catch { /* skip */ }
    // Default gateway
    let defaultGateway = null;
    let defaultIfaceName = null;
    try {
        const ps3 = `Get-NetRoute -DestinationPrefix '0.0.0.0/0' | Sort-Object RouteMetric | Select-Object -First 1 NextHop,InterfaceAlias | ConvertTo-Json`;
        const { stdout } = await execAsync(`powershell -NonInteractive -Command "${ps3}" 2>nul`);
        const route = JSON.parse(stdout);
        if (route && route.NextHop && route.NextHop !== '0.0.0.0') {
            defaultGateway = route.NextHop;
            defaultIfaceName = route.InterfaceAlias;
            const iface = interfaces.find(i => i.interfaceName === defaultIfaceName);
            if (iface) {
                iface.gateway = defaultGateway;
                iface.isDefaultRoute = true;
            }
        }
    }
    catch { /* skip */ }
    // DNS servers
    try {
        const ps4 = `Get-DnsClientServerAddress -AddressFamily IPv4 | Select-Object InterfaceAlias,ServerAddresses | ConvertTo-Json`;
        const { stdout } = await execAsync(`powershell -NonInteractive -Command "${ps4}" 2>nul`);
        const dnsData = JSON.parse(stdout);
        const dnsList = Array.isArray(dnsData) ? dnsData : [dnsData];
        for (const d of dnsList) {
            const iface = interfaces.find(i => i.interfaceName === d.InterfaceAlias);
            if (iface && Array.isArray(d.ServerAddresses) && d.ServerAddresses.length) {
                iface.dnsServers = d.ServerAddresses.filter((s) => s && s !== '0.0.0.0');
            }
        }
    }
    catch { /* skip */ }
    // WiFi details via netsh
    try {
        const { stdout } = await execAsync('netsh wlan show interfaces 2>nul');
        const ssidM = stdout.match(/\bSSID\s*:\s*(.+)/i);
        const bssidM = stdout.match(/\bBSSID\s*:\s*([\da-fA-F-:]+)/i);
        const signalM = stdout.match(/Signal\s*:\s*(\d+)%/i);
        const channelM = stdout.match(/Channel\s*:\s*(\d+)/i);
        const rxM = stdout.match(/Receive rate.*:\s*([\d.]+)/i);
        const txM = stdout.match(/Transmit rate.*:\s*([\d.]+)/i);
        const radioM = stdout.match(/Radio type\s*:\s*(.+)/i);
        const authM = stdout.match(/Authentication\s*:\s*(.+)/i);
        const wifiIface = interfaces.find(i => i.interfaceType === 'wifi');
        if (wifiIface) {
            if (ssidM)
                wifiIface.ssid = ssidM[1].trim();
            if (bssidM)
                wifiIface.bssid = bssidM[1].trim();
            if (signalM) {
                const q = parseInt(signalM[1]);
                wifiIface.signalQuality = q;
                wifiIface.rssiDbm = Math.round(q / 2 - 100);
            }
            if (channelM)
                wifiIface.channel = parseInt(channelM[1]);
            if (rxM)
                wifiIface.rxRateMbps = parseFloat(rxM[1]);
            if (txM)
                wifiIface.txRateMbps = parseFloat(txM[1]);
            if (radioM) {
                const r = radioM[1].trim();
                wifiIface.wifiBand = r.includes('5') ? '5GHz' : '2.4GHz';
            }
            if (authM)
                wifiIface.wifiSecurity = authM[1].trim();
        }
    }
    catch { /* skip */ }
    // WireGuard VPN detection via wg show
    try {
        const { stdout } = await execAsync('wg show 2>nul');
        if (stdout.trim()) {
            // Find matching interface
            const ifaceNameM = stdout.match(/^interface:\s*(\S+)/m);
            if (ifaceNameM) {
                const wgName = ifaceNameM[1];
                let wgIface = interfaces.find(i => i.interfaceName === wgName);
                if (!wgIface) {
                    wgIface = { interfaceName: wgName, interfaceType: 'vpn', ipAddresses: [], status: 'up' };
                    interfaces.push(wgIface);
                }
                wgIface.interfaceType = 'vpn';
                wgIface.vpnType = 'wireguard';
                const epM = stdout.match(/endpoint:\s*(\S+)/);
                if (epM)
                    wgIface.vpnEndpoint = epM[1].replace(/:\d+$/, '');
                const hsM = stdout.match(/latest handshake:\s*(.+)/);
                if (hsM) {
                    const hs = hsM[1].trim();
                    wgIface.vpnLastHandshake = hs;
                    wgIface.vpnConnected = !hs.includes('Never');
                }
                else {
                    wgIface.vpnConnected = wgIface.status === 'up';
                }
            }
        }
    }
    catch { /* skip */ }
    // Network byte counters via Get-NetAdapterStatistics
    try {
        const ps5 = `Get-NetAdapterStatistics | Select-Object Name,ReceivedBytes,ReceivedUnicastPackets,ReceivedDiscardedPackets,ReceivedPacketErrors,SentBytes,SentUnicastPackets,SentDiscardedPackets,SentPacketErrors | ConvertTo-Json`;
        const { stdout } = await execAsync(`powershell -NonInteractive -Command "${ps5}" 2>nul`);
        const stats = JSON.parse(stdout);
        const statList = Array.isArray(stats) ? stats : [stats];
        for (const s of statList) {
            const iface = interfaces.find(i => i.interfaceName === s.Name);
            if (iface) {
                iface.rxBytes = s.ReceivedBytes || 0;
                iface.rxPackets = s.ReceivedUnicastPackets || 0;
                iface.rxErrors = (s.ReceivedPacketErrors || 0) + (s.ReceivedDiscardedPackets || 0);
                iface.txBytes = s.SentBytes || 0;
                iface.txPackets = s.SentUnicastPackets || 0;
                iface.txErrors = (s.SentPacketErrors || 0) + (s.SentDiscardedPackets || 0);
            }
        }
    }
    catch { /* skip — older Windows or no permission */ }
    // Gateway latency
    if (defaultGateway) {
        const latency = await pingGateway(defaultGateway);
        if (latency !== null) {
            const iface = interfaces.find(i => i.isDefaultRoute);
            if (iface)
                iface.latencyMs = latency;
        }
    }
    return interfaces.filter(i => i.interfaceType !== 'loopback');
}
class NetworkInterfaceCollector {
    async collect() {
        const platform = os.platform();
        let interfaces = [];
        try {
            if (platform === 'win32') {
                interfaces = await collectWindowsInterfaces();
            }
            else {
                interfaces = await collectLinuxInterfaces();
            }
        }
        catch (e) {
            console.warn('[net-interfaces] collection failed:', e.message);
        }
        return { interfaces, collectedAt: new Date().toISOString(), platform };
    }
}
exports.NetworkInterfaceCollector = NetworkInterfaceCollector;
//# sourceMappingURL=network-interfaces.js.map