'use strict';

var fs = require('fs');
var promises = require('fs/promises');
var path = require('path');
var normalFuncs = require('./normalFuncs.cjs');

/*========================*
 * JSON Operations
 *========================*/

/**
 * Reads and parses a JSON file.
 * Throws an error if the file content is not valid JSON.
 * @param {string} filePath
 * @returns {Promise<any>}
 */
async function readJsonFileAsync(filePath) {
  if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
  const content = await promises.readFile(filePath, 'utf-8');
  return JSON.parse(content);
}

/**
 * Saves an object as JSON to a file.
 * Automatically creates the directory if it does not exist.
 * @param {string} filePath
 * @param {any} data
 * @param {number} [spaces=2]
 * @returns {Promise<void>}
 */
function writeJsonFileAsync(filePath, data, spaces = 2) {
  const json = JSON.stringify(data, null, spaces);
  return promises.writeFile(filePath, json, 'utf-8');
}

/*========================*
 * Directory Management
 *========================*/

/**
 * Clears all contents inside a directory but keeps the directory.
 * @param {string} dirPath
 */
async function clearDirectoryAsync(dirPath) {
  if (!fs.existsSync(dirPath)) return;
  const files = await promises.readdir(dirPath);

  /** @type {Record<string, import('fs').Stats>} */
  const dataList = {};
  const promises$1 = [];

  for (const file of files) {
    const fullPath = path.join(dirPath, file);
    const lsResult = promises.lstat(fullPath);

    lsResult.then((statData) => {
      dataList[fullPath] = statData;
      return statData;
    });
    promises$1.push(lsResult);
  }

  await Promise.all(promises$1);
  const promises2 = [];
  for (const fullPath in dataList) {
    const statData = dataList[fullPath];
    if (statData.isDirectory()) {
      promises2.push(promises.rm(fullPath, { recursive: true, force: true }));
    } else {
      promises2.push(promises.unlink(fullPath));
    }
  }

  await Promise.all(promises2);
}

/*========================*
 * File Checks
 *========================*/

/**
 * Checks whether a directory is empty.
 * @param {string} dirPath
 * @returns {Promise<boolean>}
 */
async function isDirEmptyAsync(dirPath) {
  const data = await promises.readdir(dirPath);
  return data.length === 0;
}

/*========================*
 * File Operations
 *========================*/

/**
 * Copies a file to a destination.
 * @param {string} src
 * @param {string} dest
 * @param {number} [mode]
 * @returns {Promise<void>}
 */
function ensureCopyFileAsync(src, dest, mode) {
  normalFuncs.ensureDirectory(path.dirname(dest));
  return promises.copyFile(src, dest, mode);
}

/**
 * Deletes a file (If the file exists).
 * @param {string} filePath
 * @returns {Promise<boolean>}
 */
async function tryDeleteFileAsync(filePath) {
  if (normalFuncs.fileExists(filePath)) {
    await promises.unlink(filePath);
    return true;
  }
  return false;
}

/*========================*
 * Text Operations
 *========================*/

/**
 * Writes text to a file (Ensures that the directory exists, creating it recursively if needed).
 * @param {string} filePath
 * @param {string} content
 * @param {import('fs').WriteFileOptions} [ops='utf-8']
 * @returns {Promise<void>}
 */
function writeTextFileAsync(filePath, content, ops = 'utf-8') {
  const dir = path.dirname(filePath);
  normalFuncs.ensureDirectory(dir);
  return promises.writeFile(filePath, content, ops);
}

/*========================*
 * Directory Listings
 *========================*/

/**
 * Lists all files and dirs in a directory (optionally recursive).
 * @param {string} dirPath
 * @param {boolean} [recursive=false]
 * @returns {Promise<{ files: string[]; dirs: string[] }>}
 */
async function listFilesAsync(dirPath, recursive = false) {
  /** @type {{ files: string[]; dirs: string[] }} */
  const results = { files: [], dirs: [] };
  if (!normalFuncs.dirExists(dirPath)) return results;

  const entries = await promises.readdir(dirPath);
  for (const entry of entries) {
    const fullPath = path.join(dirPath, entry);
    const statData = await promises.lstat(fullPath);
    if (statData.isDirectory()) {
      results.dirs.push(fullPath);
      if (recursive) {
        const results2 = await listFilesAsync(fullPath, true);
        results.files.push(...results2.files);
        results.dirs.push(...results2.dirs);
      }
    } else {
      results.files.push(fullPath);
    }
  }
  return results;
}

/**
 * Lists all directories in a directory (optionally recursive).
 * @param {string} dirPath
 * @param {boolean} [recursive=false]
 * @returns {Promise<string[]>}
 */
async function listDirsAsync(dirPath, recursive = false) {
  /** @type {string[]} */
  const results = [];
  if (!normalFuncs.dirExists(dirPath)) return results;

  const entries = await promises.readdir(dirPath);
  for (const entry of entries) {
    const fullPath = path.join(dirPath, entry);
    const statData = await promises.lstat(fullPath);
    if (statData.isDirectory()) {
      results.push(fullPath);
      if (recursive) {
        results.push(...(await listDirsAsync(fullPath, true)));
      }
    }
  }
  return results;
}

/*========================*
 * File Info
 *========================*/

/**
 * Returns the size of a file in bytes.
 * @param {string} filePath
 * @returns {Promise<number>}
 */
async function fileSizeAsync(filePath) {
  if (!normalFuncs.fileExists(filePath)) return 0;
  const stats = await promises.stat(filePath);
  return stats.size;
}

/**
 * Returns the total size of a directory in bytes.
 * @param {string} dirPath
 * @returns {Promise<number>}
 */
async function dirSizeAsync(dirPath) {
  let total = 0;
  const { files } = await listFilesAsync(dirPath, true);
  const promises = [];
  for (const file of files) {
    const result = fileSizeAsync(file);
    result.then((item) => {
      total += item;
      return item;
    });
    promises.push(result);
  }
  await Promise.all(promises);
  return total;
}

/*========================*
 * Backup Utilities
 *========================*/

/**
 * Restores the most recent backup of a file.
 * @param {string} filePath
 * @param {string} [ext='bak']
 * @returns {Promise<void>}
 */
function restoreLatestBackupAsync(filePath, ext = 'bak') {
  const latestBackup = normalFuncs.getLatestBackupPath(filePath, ext);
  return ensureCopyFileAsync(latestBackup, filePath);
}

/**
 * Creates a backup copy of a file with .bak timestamp suffix.
 * @param {string} filePath
 * @param {string} [ext='bak']
 * @returns {Promise<void>}
 */
async function backupFileAsync(filePath, ext = 'bak') {
  if (!normalFuncs.fileExists(filePath)) return;
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
  const backupPath = `${filePath}.${ext}.${timestamp}`;
  return ensureCopyFileAsync(filePath, backupPath);
}

exports.backupFileAsync = backupFileAsync;
exports.clearDirectoryAsync = clearDirectoryAsync;
exports.dirSizeAsync = dirSizeAsync;
exports.ensureCopyFileAsync = ensureCopyFileAsync;
exports.fileSizeAsync = fileSizeAsync;
exports.isDirEmptyAsync = isDirEmptyAsync;
exports.listDirsAsync = listDirsAsync;
exports.listFilesAsync = listFilesAsync;
exports.readJsonFileAsync = readJsonFileAsync;
exports.restoreLatestBackupAsync = restoreLatestBackupAsync;
exports.tryDeleteFileAsync = tryDeleteFileAsync;
exports.writeJsonFileAsync = writeJsonFileAsync;
exports.writeTextFileAsync = writeTextFileAsync;
