/**
 * Function/Windows/process-viewer.js
 * ---------------------------------------------------------------------------
 * PROCESS VIEWER
 *
 * Running processes with memory, CPU time, PID and executable path, plus
 * search by name or PID. Ending a process is an advanced, confirmed action
 * that refuses critical Windows processes and is blocked in Safe Mode.
 * ---------------------------------------------------------------------------
 */

'use strict';

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

/** Processes that must never be terminated from this tool. */
const CRITICAL = [
  'system', 'system idle process', 'registry', 'smss', 'csrss', 'wininit', 'winlogon',
  'services', 'lsass', 'lsaiso', 'fontdrvhost', 'dwm', 'sihost', 'ctfmon',
  'explorer', 'svchost', 'memory compression', 'securityhealthservice', 'msmpeng',
];

const QUERY = [
  '$ErrorActionPreference = "SilentlyContinue"',
  '@(Get-Process | Select-Object @{n="Name";e={$_.ProcessName}}, Id, @{n="Memory";e={$_.WorkingSet64}}, @{n="Cpu";e={ if ($_.CPU) { [math]::Round($_.CPU,1) } else { 0 } }}, @{n="Threads";e={$_.Threads.Count}}, @{n="Path";e={ try { $_.MainModule.FileName } catch { $null } }}, @{n="Started";e={ try { $_.StartTime.ToString("yyyy-MM-dd HH:mm:ss") } catch { $null } }}, @{n="Title";e={$_.MainWindowTitle}}) | ConvertTo-Json -Depth 4 -Compress',
].join('\n');

async function collect() {
  const result = await system.runPowerShellJson(QUERY, { timeoutMs: 60000 });
  if (!result.ok) return null;
  return system.asArray(result.data).map((process) => ({
    name: String(process.Name || ''),
    pid: Number(process.Id),
    memory: Number(process.Memory) || 0,
    cpu: Number(process.Cpu) || 0,
    threads: Number(process.Threads) || 0,
    path: system.clean(process.Path),
    started: system.clean(process.Started),
    title: system.clean(process.Title),
  }));
}

function isCritical(process) {
  return CRITICAL.includes(String(process.name).toLowerCase());
}

const SORTS = [
  { label: 'Memory (highest first)', compare: (a, b) => b.memory - a.memory },
  { label: 'CPU time (highest first)', compare: (a, b) => b.cpu - a.cpu },
  { label: 'Name (A-Z)', compare: (a, b) => a.name.localeCompare(b.name) },
  { label: 'Process ID', compare: (a, b) => a.pid - b.pid },
];

function renderProcesses(ui, processes, limit) {
  kit.table(
    ['PROCESS', 'PID', 'MEMORY', 'CPU (s)', 'THREADS'],
    processes.map((process) => [
      ui.c.brightWhite(ui.truncate(process.name, 26)),
      ui.c.gray(String(process.pid)),
      ui.c.brightCyan(system.formatBytes(process.memory)),
      ui.c.gray(String(process.cpu)),
      ui.c.gray(String(process.threads)),
    ]),
    { align: ['left', 'right', 'right', 'right', 'right'], limit }
  );
}

module.exports = {
  name: 'Process Viewer',
  menuName: 'Process Viewer',
  description: 'Running processes with memory, CPU time, path and search. Ending one is confirmed.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'Windows',
  order: 80,
  requiredPermissions: 'normal',
  destructive: true,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['system:read', 'process:read', 'process:terminate(confirmed)'],
  tags: ['process', 'task', 'memory', 'cpu', 'pid', 'search'],
  documentation:
    'Lists the running processes with memory usage, accumulated CPU time, thread count, executable ' +
    'path and start time, sortable by any of those and searchable by name or PID. Ending a process ' +
    'is possible but treated as an advanced action: it requires a confirmation, is blocked in Safe ' +
    'Mode and in Dry Run, and a list of critical Windows processes (winlogon, csrss, lsass, ' +
    'services, explorer, svchost and others) can never be ended through this tool.',

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

  async execute(ctx) {
    const { ui, prompt } = ctx;
    kit.header('Process Viewer', ['Running processes and what they use.']);
    if (!kit.requireWindows('The process viewer')) return { ok: false, reason: 'unsupported-platform' };

    const spin = ui.spinner('Reading processes...');
    let processes = await collect();
    spin.stop();
    if (!processes || !processes.length) return kit.noData('process information');

    let view = [...processes].sort(SORTS[0].compare);
    let title = 'PROCESSES BY MEMORY';

    for (;;) {
      const totalMemory = processes.reduce((sum, process) => sum + process.memory, 0);

      ui.blank();
      ui.section('SUMMARY');
      ui.blank();
      kit.kv('Running Processes', system.formatNumber(processes.length));
      kit.kv('Memory In Use By Processes', system.formatBytes(totalMemory));
      kit.kv('Windows With A Title', String(processes.filter((p) => p.title).length));

      ui.blank();
      ui.section(title);
      ui.blank();
      renderProcesses(ui, view, 20);

      ui.blank();
      ui.menu(
        [
          { key: '1', label: 'Search by name or PID' },
          { key: '2', label: 'Sort the list' },
          { key: '3', label: 'Show process details' },
          { key: '4', label: 'Refresh' },
          { key: '5', label: 'Export the current view' },
          { key: '6', label: 'End a process (advanced)', color: 'brightYellow' },
          { separator: true },
          { key: '0', label: 'Back', color: 'gray' },
        ],
        { title: 'PROCESS VIEWER' }
      );
      ui.blank();

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

      switch (answer) {
        case '1': {
          const term = (await prompt.text('Process name or PID')).trim().toLowerCase();
          if (!term) break;
          view = processes.filter(
            (process) => process.name.toLowerCase().includes(term) || String(process.pid) === term
          );
          title = `SEARCH: ${term.toUpperCase()} (${view.length})`;
          if (!view.length) {
            ui.blank();
            ui.warning('No process matches.');
            view = [...processes].sort(SORTS[0].compare);
            title = 'PROCESSES BY MEMORY';
            await prompt.pause();
          }
          break;
        }

        case '2': {
          const index = await prompt.choose('Sort by', SORTS.map((s) => s.label));
          if (index === null) break;
          view = [...view].sort(SORTS[index].compare);
          title = `SORTED BY ${SORTS[index].label.toUpperCase()}`;
          break;
        }

        case '3': {
          const index = await prompt.choose(
            'Which process?',
            view.slice(0, 30).map((process) => ({ label: `${process.name} (PID ${process.pid})`, hint: system.formatBytes(process.memory) }))
          );
          if (index === null) break;
          const process = view[index];
          ui.blank();
          ui.section(`${process.name.toUpperCase()} (PID ${process.pid})`);
          ui.blank();
          kit.kv('Process Name', process.name);
          kit.kv('Process ID', String(process.pid));
          kit.kv('Memory (working set)', system.formatBytes(process.memory));
          kit.kv('CPU Time', `${process.cpu} s`);
          kit.kv('Threads', String(process.threads));
          kit.kv('Started', process.started);
          kit.kv('Window Title', process.title);
          kit.kv('Critical Process', isCritical(process) ? 'Yes - cannot be ended here' : 'No');
          if (process.path) {
            ui.blank();
            ui.println('  ' + ui.c.gray('Executable path:'));
            ui.println('  ' + ui.c.white(ui.truncate(process.path, ui.getWidth())));
          } else {
            ui.blank();
            ui.println('  ' + ui.c.gray('The executable path needs administrator rights for this process.'));
          }
          await prompt.pause();
          break;
        }

        case '4': {
          const refresh = ui.spinner('Refreshing...');
          processes = (await collect()) || processes;
          refresh.stop();
          view = [...processes].sort(SORTS[0].compare);
          title = 'PROCESSES BY MEMORY';
          break;
        }

        case '5': {
          const report = kit.report('Process Report');
          const section = report.section('SUMMARY');
          section.add('Running Processes', system.formatNumber(processes.length));
          section.add('Memory In Use', system.formatBytes(processes.reduce((sum, p) => sum + p.memory, 0)));
          section.add('View', title);
          const list = report.section('PROCESSES');
          view.forEach((process, index) => {
            list.add(
              `${index + 1}. ${process.name} (PID ${process.pid})`,
              `${system.formatBytes(process.memory)} | CPU ${process.cpu}s | ${process.path || 'path unavailable'}`
            );
          });
          kit.saveReport(report, 'Processes');
          await prompt.pause();
          break;
        }

        case '6': {
          if (ctx.safeMode) {
            ui.blank();
            ui.warning('Safe Mode is enabled - ending a process is blocked.');
            await prompt.pause();
            break;
          }
          const index = await prompt.choose(
            'Which process should be ended?',
            view.slice(0, 30).map((process) => ({
              label: `${process.name} (PID ${process.pid})`,
              hint: isCritical(process) ? 'critical - protected' : system.formatBytes(process.memory),
            }))
          );
          if (index === null) break;
          const process = view[index];

          if (isCritical(process)) {
            ui.blank();
            ui.error(`"${process.name}" is a critical Windows process and will not be ended.`);
            await prompt.pause();
            break;
          }
          if (ctx.dryRun) {
            ui.blank();
            ui.info(`Dry Run: "${process.name}" (PID ${process.pid}) would be ended. Nothing was changed.`);
            await prompt.pause();
            break;
          }

          ui.blank();
          ui.warning('Ending a process loses any unsaved work in that application.');
          const confirmed = await prompt.confirm(`End "${process.name}" (PID ${process.pid})?`, { danger: true });
          if (confirmed !== true) {
            ui.info('Nothing was changed.');
            await prompt.pause();
            break;
          }

          const spin2 = ui.spinner(`Ending ${process.name}...`);
          const outcome = await system.runPowerShell(
            `$ErrorActionPreference = "Stop"\ntry { Stop-Process -Id ${process.pid} -ErrorAction Stop; "OK" } catch { "ERROR: " + $_.Exception.Message }`,
            { timeoutMs: 30000 }
          );
          spin2.stop();

          const text = outcome.stdout.trim();
          ui.blank();
          if (text.startsWith('OK')) {
            ui.ok(`"${process.name}" was ended.`);
            ctx.logger.warn('process-viewer', 'Process terminated', `${process.name} (PID ${process.pid})`);
            const refresh = ui.spinner('Refreshing...');
            processes = (await collect()) || processes;
            refresh.stop();
            view = [...processes].sort(SORTS[0].compare);
            title = 'PROCESSES BY MEMORY';
          } else {
            ui.error(`The process could not be ended: ${text || outcome.stderr}`);
            if (!ctx.isAdmin) ui.println('  ' + ui.c.gray('Processes of other users need administrator rights.'));
          }
          await prompt.pause();
          break;
        }

        default:
          ui.warning('Enter one of the numbers shown above, or 0 to go back.');
          await prompt.pause();
      }
    }

    return kit.done('Process viewer closed.');
  },
};
