"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InvalidURLPathOptionError = exports.InvalidPathOptionError = exports.InvalidRootOptionUseError = exports.InvalidRootOptionError = exports.InvalidGlobPattern = exports.InvalidFileIgnoreOptionError = exports.InvalidFilesOnlyOptionError = exports.InvalidFilesSourceOptionError = exports.InvalidFilesOptionError = exports.ComponentV1ProcessingError = exports.ComponentV1 = void 0;
exports.processResourceExtensionComponent = processResourceExtensionComponent;
const node_worker_threads_1 = require("node:worker_threads");
const fast_glob_1 = __importDefault(require("fast-glob"));
const harper_logger_js_1 = __importDefault(require("../utility/logging/harper_logger.js"));
const resolveBaseURLPath_ts_1 = require("./resolveBaseURLPath.js");
const deriveGlobOptions_ts_1 = require("./deriveGlobOptions.js");
const node_path_1 = require("node:path");
const promises_1 = require("node:fs/promises");
const deriveURLPath_ts_1 = require("./deriveURLPath.js");
const micromatch_1 = require("micromatch");
class ComponentV1 {
    config;
    name;
    directory;
    module;
    resources;
    globOptions;
    patternBases;
    baseURLPath;
    constructor(options) {
        // TO DO: Unfortunately `readonly` is a TS only thing and doesn't actually enforce that these properties can't be modified.
        // Freeze these things so they can't be changed. likely do this at the end of the constructor
        this.config = options.config;
        this.name = options.name;
        this.directory = options.directory;
        this.module = options.module;
        this.resources = options.resources;
        // Config option basic validation
        if (!isNonEmptyString(this.config.files) &&
            !isArrayOfNonEmptyStrings(this.config.files) &&
            !isObject(this.config.files)) {
            throw new InvalidFilesOptionError(this);
        }
        // Validating the `files` object
        if (typeof this.config.files === 'object' && !Array.isArray(this.config.files)) {
            if (this.config.files.source === undefined ||
                (!isArrayOfNonEmptyStrings(this.config.files.source) && !isNonEmptyString(this.config.files.source))) {
                throw new InvalidFilesSourceOptionError(this);
            }
            if (this.config.files.only !== undefined &&
                (typeof this.config.files.only !== 'string' ||
                    !['all', 'files', 'directories'].includes(this.config.files.only))) {
                throw new InvalidFilesOnlyOptionError(this);
            }
            if (this.config.files.ignore !== undefined &&
                !isArrayOfNonEmptyStrings(this.config.files.ignore) &&
                !isNonEmptyString(this.config.files.ignore)) {
                throw new InvalidFileIgnoreOptionError(this);
            }
        }
        // Validate the deprecated options too
        if (this.config.root !== undefined && !isNonEmptyString(this.config.root)) {
            throw new InvalidRootOptionError(this);
        }
        if (this.config.path !== undefined && !isNonEmptyString(this.config.path)) {
            throw new InvalidPathOptionError(this);
        }
        // Handle deprecated `path` option
        if (this.config.path) {
            harper_logger_js_1.default.warn(`Resource extension 'path' option is deprecated. Please replace with 'urlPath'.`);
            this.config.urlPath = this.config.path;
        }
        // Validate the `urlPath`
        if (this.config.urlPath !== undefined &&
            (!isNonEmptyString(this.config.urlPath) ||
                (typeof this.config.urlPath === 'string' && this.config.urlPath.includes('..')))) {
            throw new InvalidURLPathOptionError(this);
        }
        this.globOptions = (0, deriveGlobOptions_ts_1.deriveGlobOptions)(this.config.files);
        // Validate and transform glob patterns
        this.globOptions.source = this.globOptions.source.map((pattern) => {
            if (pattern.includes('..')) {
                throw new InvalidGlobPattern(this, pattern);
            }
            if (pattern.startsWith('/')) {
                harper_logger_js_1.default.warn(`Leading '/' in 'files' glob pattern is deprecated. For backwards compatibility purposes, it is currently transformed to the relative path of the component, but in the future will result in an error. Paths are automatically derived from the root of the component directory. Please remove (e.g. '/web/*' -> 'web/*').`);
                pattern = pattern === '/' ? '.' : pattern.slice(1);
            }
            return pattern;
        });
        this.patternBases = this.globOptions.source.map((pattern) => (0, micromatch_1.scan)(pattern).base);
        this.baseURLPath = (0, resolveBaseURLPath_ts_1.resolveBaseURLPath)(this.name, this.config.urlPath);
    }
}
exports.ComponentV1 = ComponentV1;
class ComponentV1ProcessingError extends Error {
    constructor(message, component) {
        super(`Component ${component.name} (from ${(0, node_path_1.basename)(component.directory)}) ${message}`);
    }
}
exports.ComponentV1ProcessingError = ComponentV1ProcessingError;
class InvalidFilesOptionError extends ComponentV1ProcessingError {
    constructor(component) {
        super(`'files' option must be a non-empty string, an array of non-empty strings, or an object.`, component);
    }
}
exports.InvalidFilesOptionError = InvalidFilesOptionError;
class InvalidFilesSourceOptionError extends ComponentV1ProcessingError {
    constructor(component) {
        super(`'files' object must have a non-empty 'source' property.`, component);
    }
}
exports.InvalidFilesSourceOptionError = InvalidFilesSourceOptionError;
class InvalidFilesOnlyOptionError extends ComponentV1ProcessingError {
    constructor(component) {
        super(`'files.only' option must be one of 'all', 'files', or 'directories'.`, component);
    }
}
exports.InvalidFilesOnlyOptionError = InvalidFilesOnlyOptionError;
class InvalidFileIgnoreOptionError extends ComponentV1ProcessingError {
    constructor(component) {
        super(`'files.ignore' option must be a non-empty string or an array of non-empty strings.`, component);
    }
}
exports.InvalidFileIgnoreOptionError = InvalidFileIgnoreOptionError;
class InvalidGlobPattern extends ComponentV1ProcessingError {
    constructor(component, pattern) {
        super(`'files' glob pattern must not contain '..'. Received: '${pattern}'`, component);
    }
}
exports.InvalidGlobPattern = InvalidGlobPattern;
class InvalidRootOptionError extends ComponentV1ProcessingError {
    constructor(component) {
        super(`deprecated 'root' option must be a non-empty string. Consider removing and updating 'files' glob pattern instead.`, component);
    }
}
exports.InvalidRootOptionError = InvalidRootOptionError;
class InvalidRootOptionUseError extends ComponentV1ProcessingError {
    constructor(component) {
        super(`the 'root' option is deprecated and only supported if 'files' is a singular, non-empty string. Please remove the 'root' option and modify the 'files' glob pattern instead.`, component);
    }
}
exports.InvalidRootOptionUseError = InvalidRootOptionUseError;
class InvalidPathOptionError extends ComponentV1ProcessingError {
    constructor(component) {
        super(`deprecated 'path' option must be a non-empty string. Consider replacing with 'urlPath'.`, component);
    }
}
exports.InvalidPathOptionError = InvalidPathOptionError;
class InvalidURLPathOptionError extends ComponentV1ProcessingError {
    constructor(component) {
        super(`'urlPath' option must be a non-empty string that must not contain '..'.`, component);
    }
}
exports.InvalidURLPathOptionError = InvalidURLPathOptionError;
function isNonEmptyString(value) {
    return typeof value === 'string' && value.trim() !== '';
}
function isArrayOfNonEmptyStrings(value) {
    return Array.isArray(value) && value.length !== 0 && value.every((item) => isNonEmptyString(item));
}
function isObject(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
async function handleRoots(component) {
    if (component.config.root) {
        harper_logger_js_1.default.warn(`Resource extension 'root' option is deprecated. Due to backwards compatibility reasons it does not act as assumed. The glob pattern will always be evaluated from the component directory root. The option is only used for the initial root directory handling. Please remove and modify the 'files' glob pattern instead.`);
    }
    // For backwards compatibility, we need to evaluate the root path via the existing logic. This is only valid if `root` is defined, and `files` is a strings that doesn't contain `**/*`,
    // And if that existing logic does not produce a reasonable root path to evaluate, we can consider the configure "new" and evaluate it based on a new process
    let rootPaths = [];
    if (component.config.root && typeof component.config.files !== 'string') {
        throw new InvalidRootOptionUseError(component);
    }
    // This starts old root handling
    let rootPath = component.config.root;
    if (rootPath) {
        // trim any leading slashes
        if (rootPath.startsWith('/')) {
            rootPath = rootPath.slice(1);
        }
        // add a trailing slash if it doesn't exist
        if (!rootPath.endsWith('/')) {
            rootPath += '/';
        }
    }
    const pattern = component.config.files;
    // This is still old root handling logic - operate only a singular pattern
    if (typeof pattern === 'string' && !pattern.includes('**/*')) {
        if (pattern.indexOf('/*') > -1) {
            rootPath = pattern.slice(0, pattern.indexOf('/*') + 1);
        }
        else if (pattern.indexOf('/') > -1) {
            rootPath = pattern.slice(0, pattern.lastIndexOf('/') + 1);
        }
    }
    if (rootPath)
        rootPaths.push(rootPath);
    // If old handling did not result in a root path, now use the patternRoots derived from the processed glob patterns
    if (rootPaths.length === 0) {
        // Return early if we are only processing files
        if (isObject(component.config.files) && component.config.files.only === 'files') {
            return false;
        }
        rootPaths = component.patternBases;
    }
    let hasFunctionality = false;
    for (const rootPath of rootPaths) {
        const rootPathAbsolute = (0, node_path_1.join)(component.directory, rootPath);
        if (node_worker_threads_1.isMainThread && component.module.setupDirectory) {
            hasFunctionality = await component.module.setupDirectory(component.baseURLPath, rootPathAbsolute, component.resources);
        }
        if (component.resources.isWorker && component.module.handleDirectory) {
            hasFunctionality = await component.module.handleDirectory(component.baseURLPath, rootPathAbsolute, component.resources);
        }
    }
    return hasFunctionality;
}
/**
 * Process a Resource Extension component by evaluating the files glob pattern
 * and then calling the appropriate setup/handle functions.
 */
async function processResourceExtensionComponent(component) {
    let hasFunctionality = false;
    hasFunctionality = await handleRoots(component);
    // Return early if roots were functional
    if (hasFunctionality)
        return hasFunctionality;
    const matches = await (0, fast_glob_1.default)(component.globOptions.source, {
        cwd: component.directory,
        objectMode: true,
        onlyFiles: component.globOptions.onlyFiles,
        onlyDirectories: component.globOptions.onlyDirectories,
        ignore: component.globOptions.ignore,
    });
    for (const entry of matches) {
        const absolutePath = (0, node_path_1.join)(component.directory, entry.path);
        if (entry.dirent.isDirectory()) {
            const urlPath = (0, deriveURLPath_ts_1.deriveURLPath)(component, entry.path, 'directory');
            if (node_worker_threads_1.isMainThread && component.module.setupDirectory) {
                await component.module.setupDirectory(urlPath, absolutePath, component.resources);
                hasFunctionality = true;
            }
            if (component.resources.isWorker && component.module.handleDirectory) {
                await component.module.handleDirectory(urlPath, absolutePath, component.resources);
                hasFunctionality = true;
            }
        }
        else if (entry.dirent.isFile()) {
            const urlPath = (0, deriveURLPath_ts_1.deriveURLPath)(component, entry.path, 'file');
            const contents = await (0, promises_1.readFile)(absolutePath);
            if (node_worker_threads_1.isMainThread && component.module.setupFile) {
                await component.module.setupFile(contents, urlPath, absolutePath, component.resources);
                hasFunctionality = true;
            }
            else if (component.resources.isWorker && component.module.handleFile) {
                await component.module.handleFile(contents, urlPath, absolutePath, component.resources);
                hasFunctionality = true;
            }
        }
        else {
            harper_logger_js_1.default.error(`Entry received from glob pattern match for component ${component.name} is neither a file nor a directory:`, entry);
        }
    }
    return hasFunctionality;
}
//# sourceMappingURL=ComponentV1.js.map