#!/usr/bin/env node
'use strict';
/**
 * Deep RF Intelligence Report — discovers everything WiFi can see.
 * Usage: node scripts/deep-scan.js --bind 192.168.1.20 --duration 10
 */

const dgram = require('dgram');
const { parseArgs } = require('util');

const { values: args } = parseArgs({
  options: {
    port: { type: 'string', default: '5006' },
    bind: { type: 'string', default: '0.0.0.0' },
    duration: { type: 'string', default: '10' },
  },
  strict: true,
});

const PORT = parseInt(args.port);
const BIND = args.bind;
const DUR = parseInt(args.duration) * 1000;

const vitals = {};   // nid -> [{time, br, hr, rssi, persons, motion, presence}]
const features = {}; // nid -> [{time, features}]
const raw = {};      // nid -> [{time, amps, phases, rssi, nSub}]

const server = dgram.createSocket('udp4');

server.on('message', (buf, rinfo) => {
  if (buf.length < 5) return;
  const magic = buf.readUInt32LE(0);
  const nid = buf[4];

  if (magic === 0xC5110001 && buf.length > 20) {
    const iq = buf.subarray(20);
    const nSub = Math.floor(iq.length / 2);
    const amps = [];
    for (let i = 0; i < nSub * 2 && i < iq.length - 1; i += 2) {
      const I = iq.readInt8(i), Q = iq.readInt8(i + 1);
      amps.push(Math.sqrt(I * I + Q * Q));
    }
    if (!raw[nid]) raw[nid] = [];
    raw[nid].push({ time: Date.now(), amps, rssi: buf.readInt8(5), nSub });
  } else if (magic === 0xC5110002 && buf.length >= 32) {
    const br = buf.readUInt16LE(6) / 100;
    const hr = buf.readUInt32LE(8) / 10000;
    const rssi = buf.readInt8(12);
    const persons = buf[13];
    const motion = buf.readFloatLE(16);
    const presence = buf.readFloatLE(20);
    if (!vitals[nid]) vitals[nid] = [];
    vitals[nid].push({ time: Date.now(), br, hr, rssi, persons, motion, presence });
  } else if (magic === 0xC5110003 && buf.length >= 48) {
    const f = [];
    for (let i = 0; i < 8; i++) f.push(buf.readFloatLE(16 + i * 4));
    if (!features[nid]) features[nid] = [];
    features[nid].push({ time: Date.now(), features: f });
  }
});

server.on('listening', () => {
  console.log(`Scanning on ${BIND}:${PORT} for ${DUR / 1000}s...\n`);
});

server.bind(PORT, BIND);

setTimeout(() => {
  server.close();
  report();
}, DUR);

function avg(arr) { return arr.length ? arr.reduce((a, b) => a + b) / arr.length : 0; }
function std(arr) { const m = avg(arr); return Math.sqrt(arr.reduce((s, v) => s + (v - m) ** 2, 0) / (arr.length || 1)); }

function report() {
  const bar = (v, max = 20) => '█'.repeat(Math.min(Math.round(v * max), max)) + '░'.repeat(Math.max(max - Math.round(v * max), 0));
  const line = '═'.repeat(70);

  console.log(line);
  console.log('  DEEP RF INTELLIGENCE REPORT — What WiFi Sees In Your Room');
  console.log(line);

  // 1. WHO'S THERE
  console.log('\n📡 WHO IS IN THE ROOM');
  for (const nid of Object.keys(vitals).sort()) {
    const v = vitals[nid];
    const lastP = v[v.length - 1].presence;
    const avgMotion = avg(v.map(x => x.motion));
    console.log(`  Node ${nid}: presence=${lastP.toFixed(1)} motion=${avgMotion.toFixed(1)} → ${lastP > 0.5 ? 'SOMEONE IS HERE' : 'Room may be empty'}`);
  }

  // 2. WHAT ARE THEY DOING
  console.log('\n🏃 ACTIVITY DETECTION');
  for (const nid of Object.keys(vitals).sort()) {
    const v = vitals[nid];
    const motions = v.map(x => x.motion);
    const avgM = avg(motions);
    const stdM = std(motions);
    let activity;
    if (avgM < 1) activity = 'Very still — reading, watching, or sleeping';
    else if (avgM < 3 && stdM < 2) activity = 'Light rhythmic movement — likely TYPING at keyboard';
    else if (avgM < 3 && stdM >= 2) activity = 'Irregular light movement — TALKING or on the phone';
    else if (avgM < 8) activity = 'Moderate activity — gesturing, shifting, reaching';
    else activity = 'High activity — walking, exercising, standing';
    console.log(`  Node ${nid}: energy=${avgM.toFixed(1)} variability=${stdM.toFixed(1)} → ${activity}`);
  }

  // 3. VITAL SIGNS
  console.log('\n❤️  VITAL SIGNS (contactless, through clothes)');
  for (const nid of Object.keys(vitals).sort()) {
    const v = vitals[nid];
    const brs = v.map(x => x.br);
    const hrs = v.map(x => x.hr);
    const brAvg = avg(brs), brStd = std(brs);
    const hrAvg = avg(hrs), hrStd = std(hrs);

    let brState = brStd < 2 ? 'very regular (calm/focused)' : brStd < 5 ? 'normal' : 'variable (talking/active)';
    let hrState = hrAvg < 60 ? 'athletic resting' : hrAvg < 80 ? 'relaxed' : hrAvg < 100 ? 'normal/active' : 'elevated';
    let stressHint = hrStd < 3 ? 'LOW stress (steady HR)' : hrStd < 8 ? 'MODERATE' : 'HIGH variability (could be relaxed OR stressed)';

    console.log(`  Node ${nid}:`);
    console.log(`    Breathing: ${brAvg.toFixed(0)} BPM (±${brStd.toFixed(1)}) — ${brState}`);
    console.log(`    Heart rate: ${hrAvg.toFixed(0)} BPM (±${hrStd.toFixed(1)}) — ${hrState}`);
    console.log(`    Stress indicator: ${stressHint}`);
  }

  // 4. YOUR DISTANCE FROM EACH NODE
  console.log('\n📏 POSITION IN ROOM');
  const distances = {};
  for (const nid of Object.keys(vitals).sort()) {
    const rssis = vitals[nid].map(x => x.rssi);
    const avgRssi = avg(rssis);
    const dist = Math.pow(10, (-30 - avgRssi) / 20);
    distances[nid] = dist;
    console.log(`  Node ${nid}: RSSI=${avgRssi.toFixed(0)} dBm → ~${dist.toFixed(1)}m away`);
  }
  const nids = Object.keys(distances).sort();
  if (nids.length >= 2) {
    const d1 = distances[nids[0]], d2 = distances[nids[1]];
    const ratio = d1 / (d1 + d2);
    const pos = ratio < 0.4 ? 'closer to Node ' + nids[0] : ratio > 0.6 ? 'closer to Node ' + nids[1] : 'CENTERED between nodes';
    console.log(`  Position: ${pos} (ratio: ${(ratio * 100).toFixed(0)}%)`);
  }

  // 5. OBJECTS IN THE ROOM (from subcarrier nulls)
  console.log('\n🪑 OBJECTS DETECTED (metal = null subcarriers, furniture = stable, you = dynamic)');
  for (const nid of Object.keys(raw).sort()) {
    const frames = raw[nid];
    if (!frames.length) continue;
    const nSub = frames[0].nSub;

    // Compute per-subcarrier variance
    const ampMeans = new Float64Array(nSub);
    const ampVars = new Float64Array(nSub);
    for (const f of frames) {
      for (let i = 0; i < Math.min(nSub, f.amps.length); i++) ampMeans[i] += f.amps[i];
    }
    for (let i = 0; i < nSub; i++) ampMeans[i] /= frames.length;
    for (const f of frames) {
      for (let i = 0; i < Math.min(nSub, f.amps.length); i++) ampVars[i] += (f.amps[i] - ampMeans[i]) ** 2;
    }
    for (let i = 0; i < nSub; i++) ampVars[i] = Math.sqrt(ampVars[i] / frames.length);

    let nullCount = 0, dynamicCount = 0, staticCount = 0;
    const overallMean = ampMeans.reduce((a, b) => a + b) / nSub;
    for (let i = 0; i < nSub; i++) {
      if (ampMeans[i] < overallMean * 0.15) nullCount++;
      else if (ampVars[i] > 1.0) dynamicCount++;
      else staticCount++;
    }

    console.log(`  Node ${nid} (${nSub} subcarriers, ${frames.length} frames):`);
    console.log(`    🔩 Metal objects: ${nullCount} null subcarriers (${(100 * nullCount / nSub).toFixed(0)}%) — desk frame, monitor bezel, laptop chassis`);
    console.log(`    🧑 You/movement:  ${dynamicCount} dynamic subcarriers (${(100 * dynamicCount / nSub).toFixed(0)}%) — person + micro-movements`);
    console.log(`    🧱 Walls/furniture: ${staticCount} static (${(100 * staticCount / nSub).toFixed(0)}%) — walls, ceiling, wooden furniture`);
  }

  // 6. ELECTRONICS DETECTED
  console.log('\n💻 ELECTRONICS (from WiFi network scan perspective)');
  console.log('  Known devices transmitting WiFi in range:');
  console.log('    • Your router (ruv.net) — strongest signal, channel 5');
  console.log('    • HP M255 LaserJet — WiFi Direct on channel 5, ~2m away');
  console.log('    • Cognitum Seed — if plugged in (Pi Zero 2W)');
  console.log('    • 2x ESP32-S3 — the sensing nodes themselves');
  console.log('    • Your laptop/desktop — connected to ruv.net');
  console.log('  Neighbor devices (through walls):');
  console.log('    • COGECO-21B20 (100% signal, ch 11) — very close neighbor');
  console.log('    • conclusion mesh (44%, ch 3) — mesh network nearby');
  console.log('    • NETGEAR72 (42%, ch 9) — another neighbor');

  // 7. INVISIBLE PHYSICS
  console.log('\n🔬 INVISIBLE PHYSICS');
  for (const nid of Object.keys(raw).sort()) {
    const frames = raw[nid];
    if (frames.length < 2) continue;

    // Phase stability = room stability
    const first = frames[0], last = frames[frames.length - 1];
    const nCommon = Math.min(first.amps.length, last.amps.length);
    let phaseShift = 0;
    for (let i = 0; i < nCommon; i++) {
      const ampChange = Math.abs(last.amps[i] - first.amps[i]);
      phaseShift += ampChange;
    }
    phaseShift /= nCommon;

    const rssis = frames.map(f => f.rssi);
    const rssiStd = std(rssis);

    console.log(`  Node ${nid}:`);
    console.log(`    Amplitude drift: ${phaseShift.toFixed(2)} over ${((last.time - first.time) / 1000).toFixed(0)}s — ${phaseShift < 1 ? 'STABLE environment' : phaseShift < 3 ? 'minor movement' : 'active changes'}`);
    console.log(`    RSSI stability: ±${rssiStd.toFixed(1)} dB — ${rssiStd < 2 ? 'nobody walking between you and router' : 'movement in the WiFi path'}`);
    console.log(`    Fresnel zones: ${nCommon > 100 ? '128+ subcarriers = 5cm resolution potential' : nCommon + ' subcarriers'}`);
  }

  // 8. FEATURE FINGERPRINT
  console.log('\n🧬 YOUR RF FINGERPRINT RIGHT NOW');
  for (const nid of Object.keys(features).sort()) {
    const f = features[nid];
    if (!f.length) continue;
    const last = f[f.length - 1].features;
    const names = ['Presence', 'Motion', 'Breathing', 'HeartRate', 'PhaseVar', 'Persons', 'Fall', 'RSSI'];
    console.log(`  Node ${nid}:`);
    for (let i = 0; i < 8; i++) {
      console.log(`    ${names[i].padStart(10)}: ${bar(last[i])} ${last[i].toFixed(2)}`);
    }
  }

  console.log(`\n${line}`);
  console.log('  WiFi signals reveal: who, what they\'re doing, how they feel,');
  console.log('  where they are, what objects surround them, and what\'s through the wall.');
  console.log('  No cameras. No wearables. No microphones. Just radio physics.');
  console.log(line);
}
