/**
 * Function/Storage/empty-folder-finder.js
 * ---------------------------------------------------------------------------
 * EMPTY FOLDER FINDER
 *
 * Finds folders that contain no files at any depth. Viewing and exporting are
 * always safe; deleting is optional, confirmed, and blocked in Safe Mode.
 * ---------------------------------------------------------------------------
 */

'use strict';

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

/** Folders that must never be removed even when they look empty. */
const PROTECTED = [
  process.env.SystemRoot || 'C:\\Windows',
  process.env.ProgramFiles || 'C:\\Program Files',
  process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)',
  process.env.ProgramData || 'C:\\ProgramData',
  os.homedir(),
]
  .filter(Boolean)
  .map((p) => path.resolve(p).toLowerCase());

function isProtected(target) {
  const resolved = path.resolve(target).toLowerCase();
  if (PROTECTED.includes(resolved)) return true;
  if (path.parse(resolved).root.toLowerCase() === resolved) return true;
  return resolved.split(path.sep).filter(Boolean).length < 2;
}

/**
 * Collect folders that contain no file anywhere below them.
 * Returns deepest-first so a nested empty tree can be removed bottom-up.
 */
async function findEmpty(root, options = {}) {
  const empty = [];
  const errors = [];
  const started = Date.now();
  const budget = options.timeBudgetMs || 120000;
  let scanned = 0;
  let truncated = false;

  async function visit(dir, depth) {
    if (truncated) return true;
    if (Date.now() - started > budget) {
      truncated = true;
      return true;
    }

    let entries;
    try {
      entries = fs.readdirSync(dir, { withFileTypes: true });
    } catch (err) {
      errors.push({ path: dir, message: err.message });
      return true; // unreadable: treat as "not empty" so it is never deleted
    }

    scanned += 1;
    if (options.onProgress && scanned % 200 === 0) options.onProgress(scanned, dir);

    let hasContent = false;
    for (const entry of entries) {
      const full = path.join(dir, entry.name);
      if (entry.isSymbolicLink()) {
        hasContent = true;
        continue;
      }
      if (entry.isFile()) {
        hasContent = true;
      } else if (entry.isDirectory()) {
        if (depth >= (options.maxDepth || 24)) {
          hasContent = true;
          continue;
        }
        // eslint-disable-next-line no-await-in-loop
        const childHasContent = await visit(full, depth + 1);
        if (childHasContent) hasContent = true;
      }
    }

    if (!hasContent && !isProtected(dir)) {
      empty.push({ path: dir, name: path.basename(dir), depth });
    }
    return hasContent;
  }

  await visit(path.resolve(root), 0);
  empty.sort((a, b) => b.depth - a.depth);
  return { empty, errors, scanned, truncated, ms: Date.now() - started };
}

module.exports = {
  name: 'Empty Folder Finder',
  menuName: 'Empty Folder Finder',
  description: 'Finds folders without any files, with optional confirmed deletion.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'Storage',
  order: 50,
  requiredPermissions: 'normal',
  destructive: true,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['filesystem:read', 'filesystem:delete(empty folders)', 'filesystem:write(reports)'],
  tags: ['folders', 'empty', 'cleanup', 'tidy'],
  config: { timeBudgetSeconds: 120 },
  documentation:
    'A folder counts as empty when it contains no file at any depth - a folder holding only other ' +
    'empty folders is included. Unreadable folders are treated as "not empty" so they can never be ' +
    'removed. Deleting is optional, always confirmed, refuses protected locations (Windows, Program ' +
    'Files, ProgramData, the user profile and drive roots) and is blocked entirely in Safe Mode.',

  async selfTest() {
    const result = await findEmpty(os.tmpdir(), { timeBudgetMs: 3000, maxDepth: 2 });
    return { ok: true, message: `${result.empty.length} empty folder(s) in the temp folder` };
  },

  async execute(ctx) {
    const { ui, prompt, config } = ctx;
    kit.header('Empty Folder Finder', ['Find folders that contain no files.', 'Deleting is optional and confirmed.']);

    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: 'Enter another folder...', value: null },
    ];

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

    let target = options[choice].value;
    if (!target) {
      target = await prompt.text('Folder path');
      if (!target) return kit.done('Scan 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' };
    }

    const spin = ui.spinner('Looking for empty folders...');
    const result = await findEmpty(target, {
      timeBudgetMs: Math.max(10, Number(config.timeBudgetSeconds) || 120) * 1000,
      onProgress: (count, dir) => spin.update(`Scanned ${system.formatNumber(count)} folders - ${ui.truncate(dir, 36)}`),
    });
    spin.stop('OK', `${system.formatNumber(result.scanned)} folder(s) checked`);

    ui.blank();
    kit.kv('Target Directory', target);
    kit.kv('Folders Checked', system.formatNumber(result.scanned));
    kit.kv('Empty Folders', system.formatNumber(result.empty.length), result.empty.length ? 'brightYellow' : 'brightGreen');
    if (result.errors.length) kit.kv('Unreadable Folders', system.formatNumber(result.errors.length));
    if (result.truncated) ui.warning('The time budget was reached - the result is partial.');

    if (!result.empty.length) {
      ui.blank();
      ui.completed('No empty folder was found.');
      return { ok: true, empty: 0 };
    }

    ui.blank();
    ui.section('EMPTY FOLDERS');
    ui.blank();
    for (const folder of result.empty.slice(0, 25)) {
      ui.println('  ' + ui.c.gray(ui.glyphs().bullet + ' ') + ui.c.white(ui.truncate(folder.path, ui.getWidth() - 4)));
    }
    if (result.empty.length > 25) {
      ui.println('  ' + ui.c.gray(`... and ${result.empty.length - 25} more`));
    }

    // ---- actions --------------------------------------------------------
    for (;;) {
      ui.blank();
      ui.menu(
        [
          { key: '1', label: 'Export the list as a TXT report' },
          { key: '2', label: 'Delete the empty folders', color: 'brightRed' },
          { separator: true },
          { key: '0', label: 'Back', color: 'gray' },
        ],
        { title: 'WHAT NOW?' }
      );
      ui.blank();

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

      if (answer === '1') {
        const report = kit.report('Empty Folder Report');
        const section = report.section('SUMMARY');
        section.add('Target Directory', target);
        section.add('Folders Checked', system.formatNumber(result.scanned));
        section.add('Empty Folders', system.formatNumber(result.empty.length));
        const list = report.section('EMPTY FOLDERS');
        result.empty.forEach((folder, index) => list.add(`${index + 1}`, folder.path));
        kit.saveReport(report, 'EmptyFolders');
        await prompt.pause();
        continue;
      }

      if (answer === '2') {
        if (ctx.safeMode) {
          ui.blank();
          ui.warning('Safe Mode is enabled - deleting folders is blocked.');
          await prompt.pause();
          continue;
        }
        if (ctx.dryRun) {
          ui.blank();
          ui.info(`Dry Run: ${result.empty.length} folder(s) would be removed. Nothing was changed.`);
          await prompt.pause();
          continue;
        }

        ui.blank();
        ui.warning(`${result.empty.length} folder(s) will be removed. Only folders without any file are affected.`);
        const confirmed = await prompt.confirm('Delete these empty folders now?', { danger: true });
        if (confirmed !== true) {
          ui.info('Nothing was deleted.');
          continue;
        }

        let removed = 0;
        const failed = [];
        for (const folder of result.empty) {
          if (isProtected(folder.path)) continue;
          try {
            fs.rmdirSync(folder.path);
            removed += 1;
          } catch (err) {
            failed.push({ path: folder.path, error: err.message });
          }
        }

        ui.blank();
        ui.ok(`${removed} empty folder(s) removed.`);
        if (failed.length) {
          ui.warning(`${failed.length} folder(s) could not be removed (in use or protected).`);
          for (const entry of failed.slice(0, 5)) {
            ui.println('      ' + ui.c.gray(ui.truncate(entry.path, ui.getWidth() - 6)));
          }
        }
        ctx.logger.info('empty-folder-finder', 'Empty folders removed', { removed, failed: failed.length, target });
        await prompt.pause();
        break;
      }

      ui.warning('Enter 1, 2 or 0.');
    }

    return kit.done('Empty folder scan finished.');
  },
};
