"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EntryHandler = void 0;
const harper_logger_js_1 = require("../utility/logging/harper_logger.js");
const node_events_1 = require("node:events");
const Component_js_1 = require("./Component.js");
const chokidar_1 = __importDefault(require("chokidar"));
const node_path_1 = require("node:path");
const promises_1 = require("node:fs/promises");
const deriveURLPath_js_1 = require("./deriveURLPath.js");
const micromatch_1 = require("micromatch");
class EntryHandler extends node_events_1.EventEmitter {
    #component;
    #watcher;
    #logger;
    #pendingFileReads;
    #isInitialScanComplete;
    ready;
    constructor(name, directory, config, logger) {
        super();
        this.#component = new Component_js_1.Component(name, directory, castConfig(config));
        this.#logger = logger || (0, harper_logger_js_1.loggerWithTag)(name);
        this.#pendingFileReads = new Set();
        this.#isInitialScanComplete = false;
        this.ready = (0, node_events_1.once)(this, 'ready');
        this.#watch();
    }
    get name() {
        return this.#component.name;
    }
    get directory() {
        return this.#component.directory;
    }
    #handleAll(...[event, path, stats]) {
        if (path === '')
            path = '/';
        if (!(0, micromatch_1.isMatch)(path, this.#component.globOptions.source, { ignore: this.#component.globOptions.ignore }))
            return;
        const absolutePath = (0, node_path_1.join)(this.directory, path);
        switch (event) {
            case 'add':
            case 'change': {
                const urlPath = (0, deriveURLPath_js_1.deriveURLPath)(this.#component, path, 'file');
                const fileReadPromise = (0, promises_1.readFile)(absolutePath)
                    .then((contents) => {
                    const entry = {
                        eventType: event,
                        entryType: 'file',
                        contents,
                        stats,
                        absolutePath,
                        urlPath,
                    };
                    this.emit('all', entry);
                    this.emit(event, entry);
                })
                    .finally(() => {
                    this.#pendingFileReads.delete(fileReadPromise);
                    this.#checkIfAllComplete();
                });
                this.#pendingFileReads.add(fileReadPromise);
                break;
            }
            case 'unlink': {
                const urlPath = (0, deriveURLPath_js_1.deriveURLPath)(this.#component, path, 'file');
                const entry = {
                    eventType: event,
                    entryType: 'file',
                    stats,
                    absolutePath,
                    urlPath,
                };
                this.emit('all', entry);
                this.emit(event, entry);
                break;
            }
            case 'addDir':
            case 'unlinkDir': {
                const urlPath = (0, deriveURLPath_js_1.deriveURLPath)(this.#component, path, 'directory');
                const entry = {
                    eventType: event,
                    entryType: 'directory',
                    stats,
                    absolutePath,
                    urlPath,
                };
                this.emit('all', entry);
                this.emit(event, entry);
                break;
            }
        }
    }
    #handleError(error) {
        this.emit('error', error);
    }
    #handleReady() {
        this.#isInitialScanComplete = true;
        if (this.#pendingFileReads.size > 0) {
            this.#logger.debug?.(`Initial scan complete, still waiting for ${this.#pendingFileReads.size} pending file reads`);
        }
        this.#checkIfAllComplete();
    }
    #checkIfAllComplete() {
        // Only emit 'ready' once the initial scan is complete AND all file reads are done
        if (this.#isInitialScanComplete && this.#pendingFileReads.size === 0) {
            this.emit('ready');
        }
    }
    async #watch() {
        await this.#watcher?.close();
        this.#watcher = undefined;
        const allowedBases = this.#component.patternBases.map((base) => (0, node_path_1.join)(this.#component.directory, base));
        this.#watcher = chokidar_1.default
            .watch(this.#component.commonPatternBase, {
            cwd: this.#component.directory,
            persistent: false,
            ignored: (path) => {
                const normalizedPath = path.replace(/\\/g, '/');
                const normalizedBases = allowedBases.map((base) => base.replace(/\\/g, '/'));
                return (normalizedPath !== this.#component.directory.replace(/\\/g, '/') &&
                    normalizedBases.every((base) => !normalizedPath.startsWith(base)));
            },
        })
            .on('all', this.#handleAll.bind(this))
            .on('error', this.#handleError.bind(this))
            .on('ready', this.#handleReady.bind(this));
        return this.ready;
    }
    close() {
        this.#watcher?.close();
        this.#watcher = undefined;
        this.emit('close');
        this.removeAllListeners();
        return this;
    }
    update(config) {
        this.#component = new Component_js_1.Component(this.name, this.directory, castConfig(config));
        return this.#watch();
    }
}
exports.EntryHandler = EntryHandler;
function castConfig(config) {
    return typeof config === 'string' || Array.isArray(config) || !('files' in config) ? { files: config } : config;
}
//# sourceMappingURL=EntryHandler.js.map