/**
 * Function/System/cpu-monitor.js
 * ---------------------------------------------------------------------------
 * CPU MONITOR
 *
 * Processor model, cores/threads, clocks, processor id, per-core load and -
 * where the firmware exposes it - temperature. Includes a live refresh view.
 * Read-only.
 * ---------------------------------------------------------------------------
 */

'use strict';

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

const INFO_QUERY = [
  '$ErrorActionPreference = "SilentlyContinue"',
  '[ordered]@{',
  '  cpu = @(Get-CimInstance Win32_Processor | Select-Object Name, Manufacturer, Description, ProcessorId, SocketDesignation, NumberOfCores, NumberOfLogicalProcessors, MaxClockSpeed, CurrentClockSpeed, ExtClock, L2CacheSize, L3CacheSize, AddressWidth, LoadPercentage, VirtualizationFirmwareEnabled, Status, Caption);',
  '  temp = @(Get-CimInstance -Namespace "root/wmi" -ClassName MSAcpi_ThermalZoneTemperature | Select-Object InstanceName, CurrentTemperature)',
  '} | ConvertTo-Json -Depth 4 -Compress',
].join('\n');

/** Per-core load percentages (the "_Total" instance is dropped). */
const LOAD_QUERY = [
  '$ErrorActionPreference = "SilentlyContinue"',
  '@(Get-CimInstance Win32_PerfFormattedData_PerfOS_Processor | Where-Object { $_.Name -ne "_Total" } | Select-Object Name, PercentProcessorTime) | ConvertTo-Json -Compress',
].join('\n');

const TOTAL_QUERY = [
  '$ErrorActionPreference = "SilentlyContinue"',
  '[ordered]@{',
  '  load = ((Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average).Average);',
  '  clock = ((Get-CimInstance Win32_Processor | Measure-Object -Property CurrentClockSpeed -Average).Average)',
  '} | ConvertTo-Json -Compress',
].join('\n');

async function collect() {
  const result = await system.runPowerShellJson(INFO_QUERY, { timeoutMs: 45000 });
  if (!result.ok || !result.data) return null;
  return {
    cpus: system.asArray(result.data.cpu),
    temps: system.asArray(result.data.temp),
  };
}

/** ACPI reports tenths of a Kelvin. */
function toCelsius(value) {
  const raw = Number(value);
  if (!Number.isFinite(raw) || raw <= 0) return null;
  return Math.round((raw / 10 - 273.15) * 10) / 10;
}

function buildFields(cpu, temps) {
  const fields = [
    ['CPU Model', system.firstOf(cpu, ['Name'])],
    ['Manufacturer', system.firstOf(cpu, ['Manufacturer'])],
    ['Description', system.firstOf(cpu, ['Description'])],
    ['Processor ID', system.firstOf(cpu, ['ProcessorId'])],
    ['Socket', system.firstOf(cpu, ['SocketDesignation'])],
    ['Core Count', system.clean(cpu.NumberOfCores)],
    ['Thread Count', system.clean(cpu.NumberOfLogicalProcessors)],
    ['Maximum Clock', cpu.MaxClockSpeed ? `${system.formatNumber(cpu.MaxClockSpeed)} MHz` : null],
    ['Current Clock', cpu.CurrentClockSpeed ? `${system.formatNumber(cpu.CurrentClockSpeed)} MHz` : null],
    ['Bus Clock', cpu.ExtClock ? `${system.formatNumber(cpu.ExtClock)} MHz` : null],
    ['L2 Cache', cpu.L2CacheSize ? system.formatBytes(Number(cpu.L2CacheSize) * 1024) : null],
    ['L3 Cache', cpu.L3CacheSize ? system.formatBytes(Number(cpu.L3CacheSize) * 1024) : null],
    ['Address Width', cpu.AddressWidth ? `${cpu.AddressWidth}-bit` : null],
    ['Current Usage', cpu.LoadPercentage !== null && cpu.LoadPercentage !== undefined ? `${cpu.LoadPercentage} %` : null],
    [
      'Virtualization',
      cpu.VirtualizationFirmwareEnabled === undefined
        ? null
        : cpu.VirtualizationFirmwareEnabled
        ? 'Enabled in firmware'
        : 'Disabled in firmware',
    ],
    ['Status', system.firstOf(cpu, ['Status'])],
  ];

  const temperature = temps.map((t) => toCelsius(t.CurrentTemperature)).filter((v) => v !== null);
  fields.push([
    'Temperature',
    temperature.length ? `${temperature[0]} °C` : null,
  ]);
  return fields;
}

/** One line per core with a small usage bar. */
async function renderCoreLoad(ui, limit = 16) {
  const result = await system.runPowerShellJson(LOAD_QUERY, { timeoutMs: 25000 });
  const cores = result.ok ? system.asArray(result.data) : [];
  if (!cores.length) {
    ui.println('  ' + ui.c.gray('Per-core load is not available on this system.'));
    return;
  }
  const sorted = cores
    .map((core) => ({ name: String(core.Name), load: Number(core.PercentProcessorTime) || 0 }))
    .sort((a, b) => Number(a.name) - Number(b.name));

  for (const core of sorted.slice(0, limit)) {
    ui.println(
      '  ' + ui.c.brightWhite(ui.padEnd(`Core ${core.name}`, 12)) + ui.progressBar(core.load, 100, 22)
    );
  }
  if (sorted.length > limit) {
    ui.println('  ' + ui.c.gray(`... and ${sorted.length - limit} more logical processors`));
  }
}

/** Live view - refreshes until the user presses ENTER. */
async function liveView(ctx, intervalMs) {
  const { ui, prompt } = ctx;
  let stop = false;

  ui.blank();
  ui.info('Live CPU view - press ENTER to stop.');
  prompt.ask('').then(() => {
    stop = true;
  });

  while (!stop) {
    const totals = await system.runPowerShellJson(TOTAL_QUERY, { timeoutMs: 20000 });
    const load = totals.ok && totals.data ? Number(totals.data.load) : NaN;
    const clock = totals.ok && totals.data ? Number(totals.data.clock) : NaN;
    const memUsed = os.totalmem() - os.freemem();

    ui.clear();
    ui.blank();
    ui.box({ title: 'LIVE CPU MONITOR', lines: [ui.c.gray('Press ENTER to stop.')] });
    ui.blank();
    ui.println(
      '  ' + ui.c.brightWhite(ui.padEnd('CPU Usage', 14)) + ui.progressBar(Number.isFinite(load) ? load : 0, 100, 26)
    );
    ui.println(
      '  ' + ui.c.brightWhite(ui.padEnd('Memory', 14)) + ui.progressBar(memUsed, os.totalmem(), 26)
    );
    ui.blank();
    kit.kv('Current Clock', Number.isFinite(clock) ? `${system.formatNumber(Math.round(clock))} MHz` : null);
    kit.kv('Load Average', Number.isFinite(load) ? `${Math.round(load)} %` : null);
    kit.kv('Memory Used', `${system.formatBytes(memUsed)} of ${system.formatBytes(os.totalmem())}`);
    kit.kv('Uptime', system.formatDuration(os.uptime()));
    ui.blank();
    ui.section('PER-CORE LOAD');
    ui.blank();
    await renderCoreLoad(ui, 12);

    if (stop) break;
    await new Promise((resolve) => setTimeout(resolve, intervalMs));
  }

  ui.blank();
  ui.info('Live view stopped.');
}

module.exports = {
  name: 'CPU Monitor',
  menuName: 'CPU Information & Monitor',
  description: 'Processor model, cores, clocks, processor id, per-core load and temperature, with a live view.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'System',
  order: 30,
  requiredPermissions: 'normal',
  destructive: false,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['system:read', 'wmi:query', 'filesystem:write(reports)'],
  tags: ['cpu', 'processor', 'monitor', 'load', 'temperature'],
  config: { refreshSeconds: 2 },
  documentation:
    'Shows the processor model, core and thread count, current and maximum clock, cache sizes, the ' +
    'processor id and the current load - per logical processor where Windows exposes it. ' +
    'Temperature is only available when the firmware publishes an ACPI thermal zone (many desktop ' +
    'boards do not). The live view refreshes until ENTER is pressed.',

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

  reportProvider: {
    id: 'cpu',
    label: 'CPU',
    order: 30,
    async build(report) {
      const data = await collect();
      const section = report.section('CPU');
      if (!data || !data.cpus.length) {
        section.note('CPU information was not available during export.');
        return;
      }
      data.cpus.forEach((cpu, index) => {
        const target = data.cpus.length > 1 ? section.group(`Processor ${index}`) : section;
        for (const [label, value] of buildFields(cpu, data.temps)) target.add(label, value);
      });
    },
  },

  async execute(ctx) {
    const { ui, prompt, config } = ctx;
    kit.header('CPU Information & Monitor', ['Processor details, per-core load and live monitoring.']);
    if (!kit.requireWindows('The CPU monitor')) return { ok: false, reason: 'unsupported-platform' };

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

    for (;;) {
      const cpu = data.cpus[0];
      ui.blank();
      ui.section('PROCESSOR');
      ui.blank();
      for (const [label, value] of buildFields(cpu, data.temps)) kit.kv(label, value);

      ui.blank();
      ui.section('LOAD BY LOGICAL PROCESSOR');
      ui.blank();
      await renderCoreLoad(ui);

      ui.blank();
      ui.menu(
        [
          { key: '1', label: 'Refresh now' },
          { key: '2', label: 'Live view (auto refresh)' },
          { key: '3', label: 'Export as TXT report' },
          { separator: true },
          { key: '0', label: 'Back', color: 'gray' },
        ],
        { title: 'CPU MONITOR' }
      );
      ui.blank();

      const answer = await prompt.select();
      if (!answer && prompt.isClosed()) break;
      if (answer === '0') break;

      if (answer === '1') {
        const again = ui.spinner('Refreshing...');
        const fresh = await collect();
        again.stop();
        if (fresh && fresh.cpus.length) {
          data.cpus = fresh.cpus;
          data.temps = fresh.temps;
        }
        ui.clear();
      } else if (answer === '2') {
        await liveView(ctx, Math.max(1, Number(config.refreshSeconds) || 2) * 1000);
      } else if (answer === '3') {
        await kit.offerExport('CPU Information Report', 'CpuInfo', (report) => {
          const section = report.section('CPU');
          data.cpus.forEach((processor, index) => {
            const target = data.cpus.length > 1 ? section.group(`Processor ${index}`) : section;
            for (const [label, value] of buildFields(processor, data.temps)) target.add(label, value);
          });
        });
        await prompt.pause();
      } else {
        ui.warning('Enter one of the numbers shown above, or 0 to go back.');
        await prompt.pause();
      }
    }

    return kit.done('CPU monitor closed.');
  },
};
