/* IMPORT */
import { RENAME_TIMEOUT } from './constants.js';
import { FileType, TargetEvent } from './enums.js';
import Utils from './utils.js';
import WatcherLocksResolver from './watcher_locks_resolver.js';
/* MAIN */
//TODO: Use a better name for this thing, maybe "RenameDetector"
class WatcherLocker {
    /* CONSTRUCTOR */
    constructor(watcher) {
        this._watcher = watcher;
        this.reset();
    }
    /* API */
    getLockAdd(config, timeout = RENAME_TIMEOUT) {
        const { ino, targetPath, events, locks } = config;
        const emit = () => {
            const otherPath = this._watcher._poller.paths.find(ino || -1, path => path !== targetPath); // Maybe this is actually a rename in a case-insensitive filesystem
            if (otherPath && otherPath !== targetPath) {
                if (Utils.fs.getRealPath(targetPath, true) === otherPath)
                    return; //TODO: This seems a little too special-casey
                this._watcher.event(events.rename, otherPath, targetPath);
            }
            else {
                this._watcher.event(events.add, targetPath);
            }
        };
        if (!ino)
            return emit();
        const cleanup = () => {
            locks.add.delete(ino);
            WatcherLocksResolver.remove(free);
        };
        const free = () => {
            cleanup();
            emit();
        };
        WatcherLocksResolver.add(free, timeout);
        const resolve = () => {
            const unlink = locks.unlink.get(ino);
            if (!unlink)
                return; // No matching "unlink" lock found, skipping
            cleanup();
            const targetPathPrev = unlink();
            if (targetPath === targetPathPrev) {
                if (events.change) {
                    if (this._watcher._poller.stats.has(targetPath)) {
                        this._watcher.event(events.change, targetPath);
                    }
                }
            }
            else {
                this._watcher.event(events.rename, targetPathPrev, targetPath);
            }
        };
        locks.add.set(ino, resolve);
        resolve();
    }
    getLockUnlink(config, timeout = RENAME_TIMEOUT) {
        const { ino, targetPath, events, locks } = config;
        const emit = () => {
            this._watcher.event(events.unlink, targetPath);
        };
        if (!ino)
            return emit();
        const cleanup = () => {
            locks.unlink.delete(ino);
            WatcherLocksResolver.remove(free);
        };
        const free = () => {
            cleanup();
            emit();
        };
        WatcherLocksResolver.add(free, timeout);
        const overridden = () => {
            cleanup();
            return targetPath;
        };
        locks.unlink.set(ino, overridden);
        locks.add.get(ino)?.();
    }
    getLockTargetAdd(targetPath, timeout) {
        const ino = this._watcher._poller.getIno(targetPath, TargetEvent.ADD, FileType.FILE);
        return this.getLockAdd({
            ino,
            targetPath,
            events: WatcherLocker.FILE_EVENTS,
            locks: this._locksFile
        }, timeout);
    }
    getLockTargetAddDir(targetPath, timeout) {
        const ino = this._watcher._poller.getIno(targetPath, TargetEvent.ADD_DIR, FileType.DIR);
        return this.getLockAdd({
            ino,
            targetPath,
            events: WatcherLocker.DIR_EVENTS,
            locks: this._locksDir
        }, timeout);
    }
    getLockTargetUnlink(targetPath, timeout) {
        const ino = this._watcher._poller.getIno(targetPath, TargetEvent.UNLINK, FileType.FILE);
        return this.getLockUnlink({
            ino,
            targetPath,
            events: WatcherLocker.FILE_EVENTS,
            locks: this._locksFile
        }, timeout);
    }
    getLockTargetUnlinkDir(targetPath, timeout) {
        const ino = this._watcher._poller.getIno(targetPath, TargetEvent.UNLINK_DIR, FileType.DIR);
        return this.getLockUnlink({
            ino,
            targetPath,
            events: WatcherLocker.DIR_EVENTS,
            locks: this._locksDir
        }, timeout);
    }
    reset() {
        this._locksAdd = new Map();
        this._locksAddDir = new Map();
        this._locksUnlink = new Map();
        this._locksUnlinkDir = new Map();
        this._locksDir = { add: this._locksAddDir, unlink: this._locksUnlinkDir };
        this._locksFile = { add: this._locksAdd, unlink: this._locksUnlink };
    }
}
WatcherLocker.DIR_EVENTS = {
    add: TargetEvent.ADD_DIR,
    rename: TargetEvent.RENAME_DIR,
    unlink: TargetEvent.UNLINK_DIR
};
WatcherLocker.FILE_EVENTS = {
    add: TargetEvent.ADD,
    change: TargetEvent.CHANGE,
    rename: TargetEvent.RENAME,
    unlink: TargetEvent.UNLINK
};
/* EXPORT */
export default WatcherLocker;
