/**
 * Function/System/usb-info.js
 * ---------------------------------------------------------------------------
 * USB DEVICE INFORMATION
 *
 * Currently connected USB devices, the USB controllers, and - purely as
 * diagnostic information - the USB storage devices Windows has seen before.
 * Read-only: nothing is ejected, disabled or removed.
 * ---------------------------------------------------------------------------
 */

'use strict';

const kit = require('../../core/kit');
const system = require('../../core/system');

const QUERY = [
  '$ErrorActionPreference = "SilentlyContinue"',
  '[ordered]@{',
  '  devices = @(Get-CimInstance Win32_PnPEntity -Filter "PNPClass = \'USB\' OR PNPClass = \'USBDevice\'" | Select-Object Name, Manufacturer, DeviceID, PNPDeviceID, Status, Service, PNPClass);',
  '  hubs = @(Get-CimInstance Win32_USBController | Select-Object Name, Manufacturer, DeviceID, Status);',
  '  storage = @(Get-CimInstance Win32_DiskDrive -Filter "InterfaceType = \'USB\'" | Select-Object Model, SerialNumber, Size, Status);',
  '  history = @(Get-ChildItem "HKLM:\\SYSTEM\\CurrentControlSet\\Enum\\USBSTOR" | ForEach-Object { $key = $_; Get-ChildItem $key.PSPath | ForEach-Object { $p = Get-ItemProperty $_.PSPath; [ordered]@{ Device = $key.PSChildName; Serial = $_.PSChildName; FriendlyName = $p.FriendlyName } } })',
  '} | ConvertTo-Json -Depth 5 -Compress',
].join('\n');

async function collect() {
  const result = await system.runPowerShellJson(QUERY, { timeoutMs: 60000 });
  if (!result.ok || !result.data) return null;
  return {
    devices: system.asArray(result.data.devices),
    hubs: system.asArray(result.data.hubs),
    storage: system.asArray(result.data.storage),
    history: system.asArray(result.data.history),
  };
}

/** VID/PID out of a USB device id, when present. */
function vendorId(deviceId) {
  const match = String(deviceId || '').match(/VID_([0-9A-F]{4})&PID_([0-9A-F]{4})/i);
  return match ? `VID ${match[1].toUpperCase()} / PID ${match[2].toUpperCase()}` : null;
}

/** Hubs and root devices are noise for most users - keep the real peripherals. */
function isInteresting(device) {
  const name = String(device.Name || '').toLowerCase();
  return !name.includes('root hub') && !name.includes('generic usb hub');
}

module.exports = {
  name: 'USB Device Information',
  menuName: 'USB Information',
  description: 'Connected USB devices, controllers and previously seen USB storage devices.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'System',
  order: 80,
  requiredPermissions: 'normal',
  destructive: false,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['system:read', 'wmi:query', 'registry:read', 'filesystem:write(reports)'],
  tags: ['usb', 'device', 'peripheral', 'storage'],
  documentation:
    'Lists the USB devices currently attached with their vendor/product id and status, the USB ' +
    'controllers, any attached USB storage, and the USB storage devices recorded in the ' +
    'USBSTOR registry key. The history is diagnostic information only - nothing is modified.',

  async selfTest(ctx) {
    if (!ctx.system.IS_WINDOWS) return { ok: false, message: 'Windows only' };
    const data = await collect();
    return data ? { ok: true, message: `${data.devices.length} USB device(s) enumerated` } : { ok: false, message: 'no USB data' };
  },

  async execute(ctx) {
    const { ui, prompt } = ctx;
    kit.header('USB Information', ['Connected USB devices and controllers.']);
    if (!kit.requireWindows('USB information')) return { ok: false, reason: 'unsupported-platform' };

    const spin = ui.spinner('Enumerating USB devices...');
    const data = await collect();
    spin.stop();
    if (!data) return kit.noData('USB information');

    const peripherals = data.devices.filter(isInteresting);
    const problems = data.devices.filter((d) => system.clean(d.Status) && system.clean(d.Status) !== 'OK');

    ui.section('SUMMARY');
    ui.blank();
    kit.kv('USB Devices', String(data.devices.length));
    kit.kv('Peripherals', String(peripherals.length));
    kit.kv('USB Controllers', String(data.hubs.length));
    kit.kv('USB Storage Attached', String(data.storage.length));
    kit.kv('Known Storage History', String(data.history.length));
    if (problems.length) kit.kv('Devices With Errors', String(problems.length), 'brightRed');

    ui.blank();
    ui.section('CONNECTED USB DEVICES');
    ui.blank();
    if (!peripherals.length) {
      ui.println('  ' + ui.c.gray('No USB peripheral was found.'));
    } else {
      kit.table(
        ['DEVICE', 'VENDOR', 'ID', 'STATUS'],
        peripherals.map((device) => [
          ui.c.brightWhite(ui.truncate(system.clean(device.Name) || 'Unknown', 30)),
          ui.c.gray(ui.truncate(system.clean(device.Manufacturer) || '-', 18)),
          ui.c.gray(vendorId(device.PNPDeviceID) || '-'),
          system.clean(device.Status) === 'OK' ? ui.c.brightGreen('OK') : ui.c.brightRed(system.clean(device.Status) || '?'),
        ]),
        { limit: 25 }
      );
    }

    if (data.storage.length) {
      ui.blank();
      ui.section('ATTACHED USB STORAGE');
      ui.blank();
      for (const drive of data.storage) {
        kit.kv(system.clean(drive.Model) || 'USB drive', `${system.formatBytes(drive.Size)}  ${system.clean(drive.Status) || ''}`);
      }
    }

    if (data.hubs.length) {
      ui.blank();
      ui.section('USB CONTROLLERS');
      ui.blank();
      kit.table(
        ['CONTROLLER', 'STATUS'],
        data.hubs.map((hub) => [
          ui.c.brightWhite(ui.truncate(system.clean(hub.Name) || 'Unknown', 44)),
          system.clean(hub.Status) === 'OK' ? ui.c.brightGreen('OK') : ui.c.yellow(system.clean(hub.Status) || '?'),
        ]),
        { limit: 12 }
      );
    }

    if (data.history.length) {
      ui.blank();
      const show = await prompt.confirm(`Show the ${data.history.length} USB storage device(s) seen before?`);
      if (show === true) {
        ui.blank();
        ui.section('USB STORAGE HISTORY');
        ui.println('  ' + ui.c.gray('Diagnostic information from the USBSTOR registry key.'));
        ui.blank();
        kit.table(
          ['DEVICE', 'SERIAL / INSTANCE'],
          data.history.map((entry) => [
            ui.c.brightWhite(ui.truncate(system.clean(entry.FriendlyName) || system.clean(entry.Device) || 'Unknown', 34)),
            ui.c.gray(ui.truncate(system.clean(entry.Serial) || '-', 24)),
          ]),
          { limit: 25 }
        );
      }
    }

    await kit.offerExport('USB Device Report', 'UsbInfo', (report) => {
      const section = report.section('USB');
      section.add('USB Devices', String(data.devices.length));
      section.add('USB Controllers', String(data.hubs.length));
      const connected = section.group('Connected Devices');
      peripherals.forEach((device, index) => {
        connected.add(
          `${index + 1}. ${system.clean(device.Name) || 'Unknown'}`,
          [system.clean(device.Manufacturer), vendorId(device.PNPDeviceID), system.clean(device.Status)]
            .filter(Boolean)
            .join(' | ')
        );
      });
      if (data.storage.length) {
        const storage = section.group('Attached USB Storage');
        data.storage.forEach((drive, index) => {
          storage.add(`${index + 1}. ${system.clean(drive.Model) || 'USB drive'}`, system.formatBytes(drive.Size));
        });
      }
      if (data.history.length) {
        const history = section.group('USB Storage History');
        data.history.forEach((entry, index) => {
          history.add(`${index + 1}. ${system.clean(entry.FriendlyName) || system.clean(entry.Device)}`, system.clean(entry.Serial));
        });
      }
    });

    return kit.done('USB information collected.');
  },
};
