"use strict";
/* IMPORT */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const constants_1 = require("./constants");
const utils_1 = __importDefault(require("./utils"));
/* WATCHER HANDLER */
class WatcherHandler {
    /* CONSTRUCTOR */
    constructor(watcher, config, base) {
        this.base = base;
        this.watcher = watcher;
        this.handler = config.handler;
        this.fswatcher = config.watcher;
        this.options = config.options;
        this.folderPath = config.folderPath;
        this.filePath = config.filePath;
        this['handlerBatched'] = this.base ? this.base.onWatcherEvent.bind(this.base) : this._makeHandlerBatched(this.options.debounce); //UGLY
    }
    /* HELPERS */
    _isSubRoot(targetPath) {
        if (this.filePath) {
            return targetPath === this.filePath;
        }
        else {
            return targetPath === this.folderPath || utils_1.default.fs.isSubPath(this.folderPath, targetPath);
        }
    }
    _makeHandlerBatched(delay = constants_1.DEBOUNCE) {
        return (() => {
            let lock = this.watcher._readyWait, // ~Ensuring no two flushes are active in parallel, or before the watcher is ready
            initials = [], regulars = new Set();
            const flush = async (initials, regulars) => {
                const initialEvents = this.options.ignoreInitial ? [] : initials, regularEvents = await this.eventsPopulate([...regulars]), events = this.eventsDeduplicate([...initialEvents, ...regularEvents]);
                this.onTargetEvents(events);
            };
            const flushDebounced = utils_1.default.lang.debounce(() => {
                if (this.watcher.isClosed())
                    return;
                lock = flush(initials, regulars);
                initials = [];
                regulars = new Set();
            }, delay);
            return async (event, targetPath = '', isInitial = false) => {
                if (isInitial) { // Poll immediately
                    await this.eventsPopulate([targetPath], initials, true);
                }
                else { // Poll later
                    regulars.add(targetPath);
                }
                lock.then(flushDebounced);
            };
        })();
    }
    /* EVENT HELPERS */
    eventsDeduplicate(events) {
        if (events.length < 2)
            return events;
        const targetsEventPrev = {};
        return events.reduce((acc, event) => {
            const [targetEvent, targetPath] = event, targetEventPrev = targetsEventPrev[targetPath];
            if (targetEvent === targetEventPrev)
                return acc; // Same event, ignoring
            if (targetEvent === "change" /* CHANGE */ && targetEventPrev === "add" /* ADD */)
                return acc; // "change" after "add", ignoring
            targetsEventPrev[targetPath] = targetEvent;
            acc.push(event);
            return acc;
        }, []);
    }
    async eventsPopulate(targetPaths, events = [], isInitial = false) {
        await Promise.all(targetPaths.map(async (targetPath) => {
            const targetEvents = await this.watcher._poller.update(targetPath, this.options.pollingTimeout);
            await Promise.all(targetEvents.map(async (event) => {
                events.push([event, targetPath]);
                if (event === "addDir" /* ADD_DIR */) {
                    await this.eventsPopulateAddDir(targetPaths, targetPath, events, isInitial);
                }
                else if (event === "unlinkDir" /* UNLINK_DIR */) {
                    await this.eventsPopulateUnlinkDir(targetPaths, targetPath, events, isInitial);
                }
            }));
        }));
        return events;
    }
    ;
    async eventsPopulateAddDir(targetPaths, targetPath, events = [], isInitial = false) {
        var _a, _b;
        if (isInitial)
            return events;
        const depth = this.options.recursive ? (_a = this.options.depth) !== null && _a !== void 0 ? _a : constants_1.DEPTH : Math.min(1, (_b = this.options.depth) !== null && _b !== void 0 ? _b : constants_1.DEPTH), [directories, files] = await utils_1.default.fs.readdir(targetPath, this.options.ignore, depth, this.watcher._closeSignal), targetSubPaths = [...directories, ...files];
        await Promise.all(targetSubPaths.map(targetSubPath => {
            if (this.watcher.isIgnored(targetSubPath, this.options.ignore))
                return;
            if (targetPaths.includes(targetSubPath))
                return;
            return this.eventsPopulate([targetSubPath], events, true);
        }));
        return events;
    }
    async eventsPopulateUnlinkDir(targetPaths, targetPath, events = [], isInitial = false) {
        if (isInitial)
            return events;
        for (const folderPathOther of this.watcher._poller.stats.keys()) {
            if (!utils_1.default.fs.isSubPath(targetPath, folderPathOther))
                continue;
            if (targetPaths.includes(folderPathOther))
                continue;
            await this.eventsPopulate([folderPathOther], events, true);
        }
        return events;
    }
    /* EVENT HANDLERS */
    onTargetAdd(targetPath) {
        if (this._isSubRoot(targetPath)) {
            if (this.options.renameDetection) {
                this.watcher._locker.getLockTargetAdd(targetPath, this.options.renameTimeout);
            }
            else {
                this.watcher.event("add" /* ADD */, targetPath);
            }
        }
    }
    onTargetAddDir(targetPath) {
        if (targetPath !== this.folderPath && this.options.recursive && (!constants_1.HAS_NATIVE_RECURSION && this.options.native !== false)) {
            this.watcher.watchDirectory(targetPath, this.options, this.handler, undefined, this.base || this);
        }
        if (this._isSubRoot(targetPath)) {
            if (this.options.renameDetection) {
                this.watcher._locker.getLockTargetAddDir(targetPath, this.options.renameTimeout);
            }
            else {
                this.watcher.event("addDir" /* ADD_DIR */, targetPath);
            }
        }
    }
    onTargetChange(targetPath) {
        if (this._isSubRoot(targetPath)) {
            this.watcher.event("change" /* CHANGE */, targetPath);
        }
    }
    onTargetUnlink(targetPath) {
        this.watcher.watchersClose(path_1.default.dirname(targetPath), targetPath, false);
        if (this._isSubRoot(targetPath)) {
            if (this.options.renameDetection) {
                this.watcher._locker.getLockTargetUnlink(targetPath, this.options.renameTimeout);
            }
            else {
                this.watcher.event("unlink" /* UNLINK */, targetPath);
            }
        }
    }
    onTargetUnlinkDir(targetPath) {
        this.watcher.watchersClose(path_1.default.dirname(targetPath), targetPath, false);
        this.watcher.watchersClose(targetPath);
        if (this._isSubRoot(targetPath)) {
            if (this.options.renameDetection) {
                this.watcher._locker.getLockTargetUnlinkDir(targetPath, this.options.renameTimeout);
            }
            else {
                this.watcher.event("unlinkDir" /* UNLINK_DIR */, targetPath);
            }
        }
    }
    onTargetEvent(event) {
        const [targetEvent, targetPath] = event;
        if (targetEvent === "add" /* ADD */) {
            this.onTargetAdd(targetPath);
        }
        else if (targetEvent === "addDir" /* ADD_DIR */) {
            this.onTargetAddDir(targetPath);
        }
        else if (targetEvent === "change" /* CHANGE */) {
            this.onTargetChange(targetPath);
        }
        else if (targetEvent === "unlink" /* UNLINK */) {
            this.onTargetUnlink(targetPath);
        }
        else if (targetEvent === "unlinkDir" /* UNLINK_DIR */) {
            this.onTargetUnlinkDir(targetPath);
        }
    }
    onTargetEvents(events) {
        for (const event of events) {
            this.onTargetEvent(event);
        }
    }
    onWatcherEvent(event, targetPath, isInitial = false) {
        return this['handlerBatched'](event, targetPath, isInitial);
    }
    onWatcherChange(event = "change" /* CHANGE */, targetName) {
        if (this.watcher.isClosed())
            return;
        const targetPath = path_1.default.resolve(this.folderPath, targetName || '');
        if (this.filePath && targetPath !== this.folderPath && targetPath !== this.filePath)
            return;
        if (this.watcher.isIgnored(targetPath, this.options.ignore))
            return;
        this.onWatcherEvent(event, targetPath);
    }
    onWatcherError(error) {
        if (constants_1.IS_WINDOWS && error.code === 'EPERM') { // This may happen when a folder is deleted
            this.onWatcherChange("change" /* CHANGE */, '');
        }
        else {
            this.watcher.error(error);
        }
    }
    /* API */
    async init() {
        await this.initWatcherEvents();
        await this.initInitialEvents();
    }
    async initWatcherEvents() {
        const onChange = this.onWatcherChange.bind(this);
        this.fswatcher.on("change" /* CHANGE */, onChange);
        const onError = this.onWatcherError.bind(this);
        this.fswatcher.on("error" /* ERROR */, onError);
    }
    async initInitialEvents() {
        var _a, _b;
        const isInitial = !this.watcher.isReady(); // "isInitial" => is ignorable via the "ignoreInitial" option
        if (this.filePath) { // Single initial path
            if (this.watcher._poller.stats.has(this.filePath))
                return; // Already polled
            await this.onWatcherEvent("change" /* CHANGE */, this.filePath, isInitial);
        }
        else { // Multiple initial paths
            const depth = this.options.recursive && (constants_1.HAS_NATIVE_RECURSION && this.options.native !== false) ? (_a = this.options.depth) !== null && _a !== void 0 ? _a : constants_1.DEPTH : Math.min(1, (_b = this.options.depth) !== null && _b !== void 0 ? _b : constants_1.DEPTH), [directories, files] = await utils_1.default.fs.readdir(this.folderPath, this.options.ignore, depth, this.watcher._closeSignal, this.options.readdirMap), targetPaths = [this.folderPath, ...directories, ...files];
            await Promise.all(targetPaths.map(targetPath => {
                if (this.watcher._poller.stats.has(targetPath))
                    return; // Already polled
                if (this.watcher.isIgnored(targetPath, this.options.ignore))
                    return;
                return this.onWatcherEvent("change" /* CHANGE */, targetPath, isInitial);
            }));
        }
    }
}
/* EXPORT */
exports.default = WatcherHandler;
//# sourceMappingURL=data:application/json;base64,[encoded data, 12592 characters]