/* IMPORT */
import fs from 'node:fs';
import path from 'node:path';
import makeCounterPromise from 'promise-make-counter';
import { NOOP_PROMISE_LIKE } from './constants.js';
import { castArray, isFunction } from './utils.js';
/* MAIN */
//TODO: Streamline the type of dirmaps
const readdir = (rootPath, options) => {
    const followSymlinks = options?.followSymlinks ?? false;
    const maxDepth = options?.depth ?? Infinity;
    const maxPaths = options?.limit ?? Infinity;
    const ignore = options?.ignore ?? [];
    const ignores = castArray(ignore).map(ignore => isFunction(ignore) ? ignore : (targetPath) => ignore.test(targetPath));
    const isIgnored = (targetPath) => ignores.some(ignore => ignore(targetPath));
    const signal = options?.signal ?? { aborted: false };
    const onDirents = options?.onDirents || (() => { });
    const directories = [];
    const directoriesNames = new Set();
    const directoriesNamesToPaths = {};
    const files = [];
    const filesNames = new Set();
    const filesNamesToPaths = {};
    const symlinks = [];
    const symlinksNames = new Set();
    const symlinksNamesToPaths = {};
    const map = {};
    const visited = new Set();
    const resultEmpty = { directories: [], directoriesNames: new Set(), directoriesNamesToPaths: {}, files: [], filesNames: new Set(), filesNamesToPaths: {}, symlinks: [], symlinksNames: new Set(), symlinksNamesToPaths: {}, map: {} };
    const result = { directories, directoriesNames, directoriesNamesToPaths, files, filesNames, filesNamesToPaths, symlinks, symlinksNames, symlinksNamesToPaths, map };
    const { promise, increment, decrement } = makeCounterPromise();
    let foundPaths = 0;
    const handleDirectory = (dirmap, subPath, name, depth) => {
        if (visited.has(subPath))
            return;
        if (foundPaths >= maxPaths)
            return;
        foundPaths += 1;
        dirmap.directories.push(subPath);
        dirmap.directoriesNames.add(name);
        // dirmap.directoriesNamesToPaths.propertyIsEnumerable(name) || ( dirmap.directoriesNamesToPaths[name] = [] );
        // dirmap.directoriesNamesToPaths[name].push ( subPath );
        directories.push(subPath);
        directoriesNames.add(name);
        directoriesNamesToPaths.propertyIsEnumerable(name) || (directoriesNamesToPaths[name] = []);
        directoriesNamesToPaths[name].push(subPath);
        visited.add(subPath);
        if (depth >= maxDepth)
            return;
        if (foundPaths >= maxPaths)
            return;
        populateResultFromPath(subPath, depth + 1);
    };
    const handleFile = (dirmap, subPath, name) => {
        if (visited.has(subPath))
            return;
        if (foundPaths >= maxPaths)
            return;
        foundPaths += 1;
        dirmap.files.push(subPath);
        dirmap.filesNames.add(name);
        // dirmap.filesNamesToPaths.propertyIsEnumerable(name) ||  ( dirmap.filesNamesToPaths[name] = [] );
        // dirmap.filesNamesToPaths[name].push ( subPath );
        files.push(subPath);
        filesNames.add(name);
        filesNamesToPaths.propertyIsEnumerable(name) || (filesNamesToPaths[name] = []);
        filesNamesToPaths[name].push(subPath);
        visited.add(subPath);
    };
    const handleSymlink = (dirmap, subPath, name, depth) => {
        if (visited.has(subPath))
            return;
        if (foundPaths >= maxPaths)
            return;
        foundPaths += 1;
        dirmap.symlinks.push(subPath);
        dirmap.symlinksNames.add(name);
        // dirmap.symlinksNamesToPaths.propertyIsEnumerable(name) || ( dirmap.symlinksNamesToPaths[name] = [] );
        // dirmap.symlinksNamesToPaths[name].push ( subPath );
        symlinks.push(subPath);
        symlinksNames.add(name);
        symlinksNamesToPaths.propertyIsEnumerable(name) || (symlinksNamesToPaths[name] = []);
        symlinksNamesToPaths[name].push(subPath);
        visited.add(subPath);
        if (!followSymlinks)
            return;
        if (depth >= maxDepth)
            return;
        if (foundPaths >= maxPaths)
            return;
        populateResultFromSymlink(subPath, depth + 1);
    };
    const handleStat = (dirmap, rootPath, name, stat, depth) => {
        if (signal.aborted)
            return;
        if (isIgnored(rootPath))
            return;
        if (stat.isDirectory()) {
            handleDirectory(dirmap, rootPath, name, depth);
        }
        else if (stat.isFile()) {
            handleFile(dirmap, rootPath, name);
        }
        else if (stat.isSymbolicLink()) {
            handleSymlink(dirmap, rootPath, name, depth);
        }
    };
    const handleDirent = (dirmap, rootPath, dirent, depth) => {
        if (signal.aborted)
            return;
        const separator = (rootPath === path.sep) ? '' : path.sep;
        const name = dirent.name;
        const subPath = `${rootPath}${separator}${name}`;
        if (isIgnored(subPath))
            return;
        if (dirent.isDirectory()) {
            handleDirectory(dirmap, subPath, name, depth);
        }
        else if (dirent.isFile()) {
            handleFile(dirmap, subPath, name);
        }
        else if (dirent.isSymbolicLink()) {
            handleSymlink(dirmap, subPath, name, depth);
        }
    };
    const handleDirents = (dirmap, rootPath, dirents, depth) => {
        for (let i = 0, l = dirents.length; i < l; i++) {
            handleDirent(dirmap, rootPath, dirents[i], depth);
        }
    };
    const populateResultFromPath = (rootPath, depth) => {
        if (signal.aborted)
            return;
        if (depth > maxDepth)
            return;
        if (foundPaths >= maxPaths)
            return;
        increment();
        fs.readdir(rootPath, { withFileTypes: true }, (error, dirents) => {
            if (error)
                return decrement();
            if (signal.aborted)
                return decrement();
            if (!dirents.length)
                return decrement();
            const promise = onDirents(dirents) || NOOP_PROMISE_LIKE;
            promise.then(() => {
                const dirmap = map[rootPath] = { directories: [], directoriesNames: new Set(), directoriesNamesToPaths: {}, files: [], filesNames: new Set(), filesNamesToPaths: {}, symlinks: [], symlinksNames: new Set(), symlinksNamesToPaths: {} };
                handleDirents(dirmap, rootPath, dirents, depth);
                decrement();
            });
        });
    };
    const populateResultFromSymlink = (rootPath, depth) => {
        increment();
        fs.realpath(rootPath, (error, realPath) => {
            if (error)
                return decrement();
            if (signal.aborted)
                return decrement();
            fs.stat(realPath, (error, stat) => {
                if (error)
                    return decrement();
                if (signal.aborted)
                    return decrement();
                const name = path.basename(realPath);
                const dirmap = map[rootPath] = { directories: [], directoriesNames: new Set(), directoriesNamesToPaths: {}, files: [], filesNames: new Set(), filesNamesToPaths: {}, symlinks: [], symlinksNames: new Set(), symlinksNamesToPaths: {} };
                handleStat(dirmap, realPath, name, stat, depth);
                decrement();
            });
        });
    };
    const getResult = async (rootPath, depth = 1) => {
        rootPath = path.normalize(rootPath);
        visited.add(rootPath);
        populateResultFromPath(rootPath, depth);
        await promise;
        if (signal.aborted)
            return resultEmpty;
        return result;
    };
    return getResult(rootPath);
};
/* EXPORT */
export default readdir;
