/**
 * Function/System/ram-info.js
 * ---------------------------------------------------------------------------
 * RAM INFORMATION
 *
 * Totals and usage plus one entry per physical memory module: capacity,
 * manufacturer, part number, serial number, speed and form factor.
 * Read-only.
 * ---------------------------------------------------------------------------
 */

'use strict';

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

const MEMORY_TYPES = {
  0: 'Unknown', 20: 'DDR', 21: 'DDR2', 22: 'DDR2 FB-DIMM', 24: 'DDR3', 26: 'DDR4',
  27: 'LPDDR', 28: 'LPDDR2', 29: 'LPDDR3', 30: 'LPDDR4', 34: 'DDR5', 35: 'LPDDR5',
};

const FORM_FACTORS = { 8: 'DIMM', 12: 'SODIMM', 13: 'SRIMM', 11: 'RIMM', 9: 'TSOP', 23: 'LGA' };

const QUERY = [
  '$ErrorActionPreference = "SilentlyContinue"',
  '[ordered]@{',
  '  modules = @(Get-CimInstance Win32_PhysicalMemory | Select-Object BankLabel, DeviceLocator, Capacity, Manufacturer, PartNumber, SerialNumber, Speed, ConfiguredClockSpeed, ConfiguredVoltage, SMBIOSMemoryType, FormFactor, DataWidth, TotalWidth);',
  '  array = (Get-CimInstance Win32_PhysicalMemoryArray | Select-Object -First 1 MemoryDevices, MaxCapacityEx);',
  '  osmem = (Get-CimInstance Win32_OperatingSystem | Select-Object -First 1 TotalVisibleMemorySize, FreePhysicalMemory, TotalVirtualMemorySize, FreeVirtualMemory);',
  '  cs = (Get-CimInstance Win32_ComputerSystem | Select-Object -First 1 TotalPhysicalMemory)',
  '} | ConvertTo-Json -Depth 4 -Compress',
].join('\n');

async function collect() {
  const result = await system.runPowerShellJson(QUERY, { timeoutMs: 45000 });
  if (!result.ok || !result.data) return null;
  return {
    modules: system.asArray(result.data.modules),
    array: result.data.array || {},
    osmem: result.data.osmem || {},
    cs: result.data.cs || {},
  };
}

function mapValue(map, value) {
  const key = Number(value);
  if (!Number.isFinite(key)) return null;
  return map[key] ? `${map[key]} (${key})` : `Code ${key}`;
}

function totals(data) {
  const total = Number(data.cs.TotalPhysicalMemory) || os.totalmem();
  const free = Number(data.osmem.FreePhysicalMemory) ? Number(data.osmem.FreePhysicalMemory) * 1024 : os.freemem();
  const used = Math.max(0, total - free);
  return { total, free, used, percent: total ? Math.round((used / total) * 100) : 0 };
}

function summaryFields(data) {
  const t = totals(data);
  return [
    ['Total RAM', system.formatBytes(t.total)],
    ['Used RAM', system.formatBytes(t.used)],
    ['Available RAM', system.formatBytes(t.free)],
    ['Usage Percentage', `${t.percent} %`],
    [
      'Memory Modules',
      data.modules.length
        ? `${data.modules.length} of ${system.clean(data.array.MemoryDevices) || data.modules.length} slot(s) in use`
        : null,
    ],
    [
      'Maximum Capacity',
      data.array.MaxCapacityEx ? system.formatBytes(Number(data.array.MaxCapacityEx) * 1024) : null,
    ],
    [
      'Virtual Memory',
      data.osmem.TotalVirtualMemorySize ? system.formatBytes(Number(data.osmem.TotalVirtualMemorySize) * 1024) : null,
    ],
  ];
}

function moduleFields(module) {
  return [
    ['Capacity', module.Capacity ? system.formatBytes(module.Capacity) : null],
    ['Manufacturer', system.firstOf(module, ['Manufacturer'])],
    ['Part Number', system.firstOf(module, ['PartNumber'])],
    ['Serial Number', system.firstOf(module, ['SerialNumber'])],
    ['Memory Type', mapValue(MEMORY_TYPES, module.SMBIOSMemoryType)],
    ['Form Factor', mapValue(FORM_FACTORS, module.FormFactor)],
    ['Rated Speed', module.Speed ? `${module.Speed} MHz` : null],
    ['Configured Speed', module.ConfiguredClockSpeed ? `${module.ConfiguredClockSpeed} MHz` : null],
    ['Voltage', module.ConfiguredVoltage ? `${module.ConfiguredVoltage} mV` : null],
    ['Bank / Slot', [system.clean(module.BankLabel), system.clean(module.DeviceLocator)].filter(Boolean).join(' / ') || null],
  ];
}

module.exports = {
  name: 'RAM Information',
  menuName: 'RAM Information',
  description: 'Total, used and available memory plus per-module capacity, speed, manufacturer and serial.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'System',
  order: 50,
  requiredPermissions: 'normal',
  destructive: false,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['system:read', 'wmi:query', 'filesystem:write(reports)'],
  tags: ['ram', 'memory', 'dimm', 'ddr'],
  documentation:
    'Shows the installed and available memory with a usage bar, then one block per physical module ' +
    'with capacity, manufacturer, part number, serial number, memory type, form factor and both the ' +
    'rated and the configured speed. Serial numbers are only exposed by some SPD implementations.',

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

  reportProvider: {
    id: 'memory',
    label: 'Memory',
    order: 50,
    async build(report) {
      const data = await collect();
      const section = report.section('MEMORY');
      if (!data) {
        section.note('Memory information was not available during export.');
        return;
      }
      for (const [label, value] of summaryFields(data)) section.add(label, value);
      data.modules.forEach((module, index) => {
        const label = system.clean(module.DeviceLocator) || `Module ${index + 1}`;
        const group = section.group(`Slot ${index + 1} - ${label}`);
        for (const [key, value] of moduleFields(module)) group.add(key, value);
      });
    },
  },

  async execute(ctx) {
    const { ui } = ctx;
    kit.header('RAM Information', ['Memory usage and the installed physical modules.']);
    if (!kit.requireWindows('RAM information')) return { ok: false, reason: 'unsupported-platform' };

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

    const t = totals(data);
    ui.section('MEMORY USAGE');
    ui.blank();
    ui.println('  ' + ui.c.brightWhite(ui.padEnd('Usage', 14)) + ui.progressBar(t.used, t.total, 26));
    ui.blank();
    for (const [label, value] of summaryFields(data)) kit.kv(label, value);

    if (!data.modules.length) {
      ui.blank();
      ui.warning('No physical memory module details were returned (common in virtual machines).');
    }

    data.modules.forEach((module, index) => {
      const label = system.clean(module.DeviceLocator) || system.clean(module.BankLabel) || `Module ${index + 1}`;
      ui.blank();
      ui.section(`SLOT ${index + 1} - ${label}`);
      ui.blank();
      for (const [key, value] of moduleFields(module)) kit.kv(key, value);
    });

    await kit.offerExport('RAM Information Report', 'RamInfo', (report) => {
      const section = report.section('MEMORY');
      for (const [label, value] of summaryFields(data)) section.add(label, value);
      data.modules.forEach((module, index) => {
        const label = system.clean(module.DeviceLocator) || `Module ${index + 1}`;
        const group = section.group(`Slot ${index + 1} - ${label}`);
        for (const [key, value] of moduleFields(module)) group.add(key, value);
      });
    });

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