/**
 * Function/Storage/downloads-analyzer.js
 * ---------------------------------------------------------------------------
 * DOWNLOADS ANALYZER
 *
 * Categorises the Downloads folder - installers, archives, images, video,
 * audio, documents - and highlights large and old files. Analysis only:
 * nothing is moved or deleted.
 * ---------------------------------------------------------------------------
 */

'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');

const CATEGORIES = [
  { id: 'installers', label: 'Installers', match: /\.(exe|msi|msix|appx|msu|bat|cmd)$/i },
  { id: 'archives', label: 'ZIP / Archives', match: /\.(zip|rar|7z|tar|gz|bz2|xz|iso|cab)$/i },
  { id: 'images', label: 'Images', match: /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|heic|tiff)$/i },
  { id: 'videos', label: 'Videos', match: /\.(mp4|mkv|avi|mov|wmv|flv|webm|m4v|mpg|mpeg)$/i },
  { id: 'audio', label: 'Audio', match: /\.(mp3|wav|flac|aac|ogg|m4a|wma)$/i },
  { id: 'documents', label: 'Documents', match: /\.(pdf|docx?|xlsx?|pptx?|txt|rtf|odt|ods|csv|md)$/i },
  { id: 'code', label: 'Code & Data', match: /\.(js|ts|py|java|cs|cpp|c|h|json|xml|yml|yaml|sql|ps1|sh)$/i },
  { id: 'other', label: 'Other', match: null },
];

const OLD_DAYS = 90;

function categorise(files) {
  const buckets = CATEGORIES.map((category) => ({ ...category, files: [], bytes: 0 }));
  const other = buckets[buckets.length - 1];

  for (const file of files) {
    const bucket = buckets.find((entry) => entry.match && entry.match.test(file.name)) || other;
    bucket.files.push(file);
    bucket.bytes += file.size;
  }
  return buckets;
}

module.exports = {
  name: 'Downloads Analyzer',
  menuName: 'Downloads Analyzer',
  description: 'Categorises the Downloads folder and highlights large and old files. Analysis only.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'Storage',
  order: 70,
  requiredPermissions: 'normal',
  destructive: false,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['filesystem:read', 'filesystem:write(reports)'],
  tags: ['downloads', 'analyze', 'cleanup', 'files'],
  config: { largeFileMb: 200, oldDays: OLD_DAYS, timeBudgetSeconds: 90 },
  documentation:
    'Walks the Downloads folder and groups everything by type - installers, archives, images, video, ' +
    'audio, documents, code and other - with the size of each group. It then lists the largest files ' +
    'and the files that have not been touched for a long time, so you can decide what to keep. ' +
    'This function never deletes or moves a file.',

  async selfTest() {
    const target = path.join(os.homedir(), 'Downloads');
    return scan.isDirectory(target)
      ? { ok: true, message: 'Downloads folder found' }
      : { ok: false, message: 'no Downloads folder' };
  },

  async execute(ctx) {
    const { ui, prompt, config } = ctx;
    kit.header('Downloads Analyzer', ['What is in your Downloads folder.', 'Nothing is deleted or moved.']);

    let target = path.join(os.homedir(), 'Downloads');
    if (!scan.isDirectory(target)) {
      ui.warning('The default Downloads folder was not found.');
      target = await prompt.text('Downloads folder path');
      if (!target || !scan.isDirectory(target)) {
        ui.error('No readable folder was given.');
        return { ok: false, reason: 'invalid-path' };
      }
    }

    const spin = ui.spinner('Analyzing downloads...');
    const result = await scan.walk(target, {
      timeBudgetMs: Math.max(10, Number(config.timeBudgetSeconds) || 90) * 1000,
      onProgress: (state) => spin.update(`Analyzing... ${system.formatNumber(state.files)} files`),
    });
    spin.stop('OK', `${system.formatNumber(result.totalFiles)} file(s) analyzed`);

    if (!result.files.length) {
      ui.blank();
      ui.info('The Downloads folder is empty.');
      return { ok: true, files: 0 };
    }

    const buckets = categorise(result.files).filter((bucket) => bucket.files.length);
    const largeThreshold = (Number(config.largeFileMb) || 200) * 1024 * 1024;
    const oldCutoff = Date.now() - (Number(config.oldDays) || OLD_DAYS) * 86400000;

    const large = result.files.filter((file) => file.size >= largeThreshold).sort((a, b) => b.size - a.size);
    const old = result.files.filter((file) => file.mtimeMs < oldCutoff).sort((a, b) => a.mtimeMs - b.mtimeMs);

    ui.blank();
    ui.section('SUMMARY');
    ui.blank();
    kit.kv('Folder', target);
    kit.kv('Files', system.formatNumber(result.totalFiles));
    kit.kv('Total Size', system.formatBytes(result.totalBytes), 'brightGreen');
    kit.kv('Large Files', `${large.length} (>= ${config.largeFileMb} MB)`);
    kit.kv('Old Files', `${old.length} (older than ${config.oldDays} days)`);
    if (result.truncated) ui.warning('The time budget was reached - the analysis is partial.');

    ui.blank();
    ui.section('BY CATEGORY');
    ui.blank();
    kit.table(
      ['CATEGORY', 'FILES', 'SIZE', 'SHARE'],
      buckets
        .sort((a, b) => b.bytes - a.bytes)
        .map((bucket) => [
          ui.c.brightWhite(bucket.label),
          ui.c.gray(system.formatNumber(bucket.files.length)),
          ui.c.brightCyan(system.formatBytes(bucket.bytes)),
          ui.c.brightYellow(result.totalBytes ? `${Math.round((bucket.bytes / result.totalBytes) * 100)}%` : '-'),
        ]),
      { align: ['left', 'right', 'right', 'right'] }
    );

    if (large.length) {
      ui.blank();
      ui.section('LARGEST FILES');
      ui.blank();
      kit.table(
        ['FILE', 'SIZE', 'MODIFIED'],
        large.map((file) => [
          ui.c.brightWhite(ui.truncate(file.name, 34)),
          ui.c.brightCyan(system.formatBytes(file.size)),
          ui.c.gray(system.isoLocal(new Date(file.mtimeMs)).slice(0, 10)),
        ]),
        { align: ['left', 'right', 'left'], limit: 12 }
      );
    }

    if (old.length) {
      ui.blank();
      ui.section(`FILES OLDER THAN ${config.oldDays} DAYS`);
      ui.blank();
      kit.table(
        ['FILE', 'SIZE', 'LAST USED'],
        old.map((file) => [
          ui.c.brightWhite(ui.truncate(file.name, 34)),
          ui.c.gray(system.formatBytes(file.size)),
          ui.c.gray(system.isoLocal(new Date(file.mtimeMs)).slice(0, 10)),
        ]),
        { align: ['left', 'right', 'left'], limit: 12 }
      );
      ui.blank();
      ui.println(
        '  ' + ui.c.gray(`Those ${old.length} old file(s) occupy ${system.formatBytes(old.reduce((s, f) => s + f.size, 0))}.`)
      );
    }

    ui.blank();
    ui.println('  ' + ui.c.gray('This is an analysis only - review the files yourself before removing anything.'));

    await kit.offerExport('Downloads Analysis', 'Downloads', (report) => {
      const summary = report.section('SUMMARY');
      summary.add('Folder', target);
      summary.add('Files', system.formatNumber(result.totalFiles));
      summary.add('Total Size', system.formatBytes(result.totalBytes));

      const categories = report.section('BY CATEGORY');
      for (const bucket of buckets) {
        categories.add(bucket.label, `${system.formatNumber(bucket.files.length)} file(s), ${system.formatBytes(bucket.bytes)}`);
      }

      const largest = report.section('LARGEST FILES');
      large.slice(0, 60).forEach((file, index) => {
        largest.add(`${index + 1}. ${file.name}`, `${system.formatBytes(file.size)} - ${system.isoLocal(new Date(file.mtimeMs))}`);
      });

      const stale = report.section('OLD FILES');
      old.slice(0, 60).forEach((file, index) => {
        stale.add(`${index + 1}. ${file.name}`, `${system.formatBytes(file.size)} - last modified ${system.isoLocal(new Date(file.mtimeMs))}`);
      });
    });

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