/**
 * Function/Storage/folder-size.js
 * ---------------------------------------------------------------------------
 * FOLDER SIZE ANALYZER
 *
 * Total size, file and folder count of a directory, plus its largest
 * sub-folders and largest files. Read-only.
 * ---------------------------------------------------------------------------
 */

'use strict';

const os = require('os');
const path = require('path');
const kit = require('../../core/kit');
const system = require('../../core/system');
const scan = require('../../core/fs-scan');

/** Group the collected files by their top-level sub-folder. */
function summariseSubFolders(root, files) {
  const map = new Map();
  const rootResolved = path.resolve(root);

  for (const file of files) {
    const relative = path.relative(rootResolved, file.dir);
    const first = relative.split(path.sep)[0];
    const key = !relative || relative.startsWith('..') ? '.' : first;
    const entry = map.get(key) || { name: key, bytes: 0, files: 0 };
    entry.bytes += file.size;
    entry.files += 1;
    map.set(key, entry);
  }

  return [...map.values()].sort((a, b) => b.bytes - a.bytes);
}

module.exports = {
  name: 'Folder Size Analyzer',
  menuName: 'Folder Size Analyzer',
  description: 'Total size, file and folder count, largest sub-folders and largest files of a directory.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'Storage',
  order: 60,
  requiredPermissions: 'normal',
  destructive: false,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['filesystem:read', 'filesystem:write(reports)'],
  tags: ['folder', 'size', 'usage', 'analyze'],
  config: { timeBudgetSeconds: 120, topEntries: 15 },
  documentation:
    'Measures one directory: total size, number of files and folders, the largest sub-folders with ' +
    'their share of the total, and the largest individual files. Symbolic links are never followed ' +
    'and unreadable folders are counted separately. Nothing is modified.',

  async selfTest() {
    const result = await scan.measure(os.tmpdir(), { timeBudgetMs: 4000, maxDepth: 2 });
    return { ok: true, message: `measured ${system.formatBytes(result.bytes)} in the temp folder` };
  },

  async execute(ctx) {
    const { ui, prompt, config } = ctx;
    kit.header('Folder Size Analyzer', ['Measure a folder and see what is inside it.']);

    const home = os.homedir();
    const options = [
      { label: `Downloads  (${path.join(home, 'Downloads')})`, value: path.join(home, 'Downloads') },
      { label: `Documents  (${path.join(home, 'Documents')})`, value: path.join(home, 'Documents') },
      { label: `User profile (${home})`, value: home },
      { label: `Application folder (${ctx.paths.root})`, value: ctx.paths.root },
      { label: 'Enter another folder...', value: null },
    ];

    const choice = await prompt.choose('Which folder should be measured?', options.map((o) => o.label));
    if (choice === null) return kit.done('Analysis cancelled.');

    let target = options[choice].value;
    if (!target) {
      target = await prompt.text('Folder path');
      if (!target) return kit.done('Analysis cancelled.');
    }
    target = String(target).replace(/^"|"$/g, '').trim();

    if (!scan.isDirectory(target)) {
      ui.blank();
      ui.error(`"${target}" is not a folder that can be read.`);
      return { ok: false, reason: 'invalid-path' };
    }

    ui.blank();
    const spin = ui.spinner('Measuring...');
    const result = await scan.walk(target, {
      timeBudgetMs: Math.max(10, Number(config.timeBudgetSeconds) || 120) * 1000,
      onProgress: (state) =>
        spin.update(`Measuring... ${system.formatNumber(state.files)} files, ${system.formatBytes(state.bytes)}`),
    });
    spin.stop('OK', `Measured in ${(result.ms / 1000).toFixed(1)}s`);

    const top = Number(config.topEntries) || 15;
    const subFolders = summariseSubFolders(target, result.files);
    const largest = [...result.files].sort((a, b) => b.size - a.size);

    ui.blank();
    ui.section('SUMMARY');
    ui.blank();
    kit.kv('Folder', target);
    kit.kv('Total Folder Size', system.formatBytes(result.totalBytes), 'brightGreen');
    kit.kv('File Count', system.formatNumber(result.totalFiles));
    kit.kv('Folder Count', system.formatNumber(result.folders));
    kit.kv(
      'Average File Size',
      result.totalFiles ? system.formatBytes(Math.round(result.totalBytes / result.totalFiles)) : null
    );
    if (result.errors.length) kit.kv('Unreadable Folders', system.formatNumber(result.errors.length));
    if (result.truncated) ui.warning('The time budget was reached - the measurement is partial.');

    if (subFolders.length) {
      ui.blank();
      ui.section('LARGEST SUB-FOLDERS');
      ui.blank();
      kit.table(
        ['SUB-FOLDER', 'SIZE', 'FILES', 'SHARE'],
        subFolders.slice(0, top).map((folder) => [
          ui.c.brightWhite(ui.truncate(folder.name === '.' ? '(files in this folder)' : folder.name, 30)),
          ui.c.brightCyan(system.formatBytes(folder.bytes)),
          ui.c.gray(system.formatNumber(folder.files)),
          ui.c.brightYellow(result.totalBytes ? `${Math.round((folder.bytes / result.totalBytes) * 100)}%` : '-'),
        ]),
        { align: ['left', 'right', 'right', 'right'], limit: top }
      );
    }

    if (largest.length) {
      ui.blank();
      ui.section('LARGEST FILES');
      ui.blank();
      kit.table(
        ['FILE', 'SIZE', 'LOCATION'],
        largest.slice(0, top).map((file) => [
          ui.c.brightWhite(ui.truncate(file.name, 28)),
          ui.c.brightCyan(system.formatBytes(file.size)),
          ui.c.gray(ui.truncate(path.relative(target, file.dir) || '.', 28)),
        ]),
        { align: ['left', 'right', 'left'], limit: top }
      );
    }

    await kit.offerExport('Folder Size Report', 'FolderSize', (report) => {
      const summary = report.section('SUMMARY');
      summary.add('Folder', target);
      summary.add('Total Folder Size', system.formatBytes(result.totalBytes));
      summary.add('File Count', system.formatNumber(result.totalFiles));
      summary.add('Folder Count', system.formatNumber(result.folders));
      summary.add('Partial Measurement', result.truncated ? 'Yes (time budget reached)' : 'No');

      const folders = report.section('LARGEST SUB-FOLDERS');
      subFolders.slice(0, 50).forEach((folder, index) => {
        folders.add(
          `${index + 1}. ${folder.name === '.' ? '(files in this folder)' : folder.name}`,
          `${system.formatBytes(folder.bytes)} in ${system.formatNumber(folder.files)} file(s)`
        );
      });

      const files = report.section('LARGEST FILES');
      largest.slice(0, 100).forEach((file, index) => {
        files.add(`${index + 1}. ${file.name}`, `${system.formatBytes(file.size)} - ${file.dir}`);
      });
    });

    return kit.done('Folder analysis finished.');
  },
};
