"use strict";
/**
 * @file Configuration and content reader for APIDoc
 *
 * Handles reading project configuration files (apidoc.json, package.json),
 * header/footer content files, and manages project metadata.
 *
 * @author Href Spa <hola@apidoc.app>
 * @copyright 2025 Href SpA
 * @license MIT
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Reader = exports.defaultConfig = void 0;
const fs = __importStar(require("fs-extra"));
const lodash_1 = require("lodash");
const path = __importStar(require("path"));
/**
 * Default configuration values used when no config file is found
 */
const defaultConfig = {
    name: 'Acme project',
    version: '0.0.0',
    description: 'REST Api',
};
exports.defaultConfig = defaultConfig;
/**
 * Configuration and content reader for APIDoc projects
 *
 * The Reader class is responsible for:
 * - Reading and parsing configuration files (apidoc.json, package.json)
 * - Loading header and footer content from markdown files
 * - Merging configuration from multiple sources with proper precedence
 * - Providing validated project metadata for documentation generation
 *
 * @example Basic usage
 * ```typescript
 * const reader = new Reader(appContext);
 * const projectInfo = reader.read();
 * console.log('Project:', projectInfo.name, 'v' + projectInfo.version);
 * ```
 *
 * @example Configuration precedence
 * ```
 * 1. apidoc.json (highest priority)
 * 2. package.json (apidoc field)
 * 3. Default values (lowest priority)
 * ```
 *
 * @since 4.0.0
 * @public
 */
class Reader {
    app;
    log;
    opt;
    /**
     * Initialize the Reader instance
     *
     * Sets up the reader with application context including logger and options.
     * The reader will use the provided configuration for file paths and processing options.
     *
     * @param app - Application context containing logger, options, and other shared state
     *
     * @example
     * ```typescript
     * const appContext: AppContext = {
     *   log: logger,
     *   options: { src: ['./src'], dest: './docs' },
     *   markdownParser: parser
     * };
     * const reader = new Reader(appContext);
     * ```
     */
    constructor(app) {
        this.app = app;
        this.log = app.log;
        this.opt = app.options;
    }
    /**
     * Reads and merges configuration from multiple sources
     *
     * This method searches for configuration files in the following order of precedence:
     * 1. `apidoc.json` - Dedicated APIDoc configuration file (highest priority)
     * 2. `apidoc.config.js` - JavaScript configuration file
     * 3. `package.json` - NPM package file with 'apidoc' field
     * 4. Default values - Built-in fallback configuration (lowest priority)
     *
     * The method also reads header and footer content files if specified in the configuration.
     *
     * @returns Merged configuration object containing:
     *   - Project metadata (name, version, description, etc.)
     *   - Header content and title (if specified)
     *   - Footer content and title (if specified)
     *   - API base URL and sample URL
     *   - Template and styling options
     *
     * @throws {Error} When configuration files exist but contain invalid JSON
     *
     * @example Configuration file precedence
     * ```
     * // 1. apidoc.json (if exists)
     * {
     *   "name": "My API",
     *   "version": "1.0.0",
     *   "header": { "filename": "header.md" }
     * }
     *
     * // 2. package.json (if apidoc.json doesn't exist)
     * {
     *   "name": "my-package",
     *   "version": "1.0.0",
     *   "apidoc": {
     *     "name": "My API Documentation"
     *   }
     * }
     * ```
     *
     * @example Return value structure
     * ```typescript
     * const config = reader.read();
     * // Returns:
     * {
     *   name: "My API",
     *   version: "1.0.0",
     *   description: "API documentation",
     *   header: {
     *     title: "Introduction",
     *     content: "<markdown content>"
     *   },
     *   footer: {
     *     title: "Contact",
     *     content: "<markdown content>"
     *   }
     * }
     * ```
     *
     * @since 4.0.0
     * @public
     */
    read() {
        let config = {};
        // if the config file is provided, we use this and do no try to read other files
        if (this.opt.config) {
            this.log.debug('Config file provided, reading this.');
            config = require(path.resolve(this.opt.config));
        }
        else {
            config = this.search();
        }
        // replace header footer with file contents
        return Object.assign(config, this.getHeaderFooter(config));
    }
    /**
     * Search for API documentation configuration files in specified directories.
     *
     * @returns Merged configuration object
     */
    search() {
        this.log.debug('Now looking for apidoc config files');
        // possible sources of information
        const sources = ['package.json', 'apidoc.json', 'apidoc.config.js'];
        // create a new object because JavaScript will not assign value
        const config = Object.assign({}, defaultConfig);
        // loop the three possible sources of information to try and find packageInfo
        sources.forEach((configFile) => {
            this.log.debug(`Now looking for ${configFile}`);
            // first look in cwd dir
            Object.assign(config, this.findConfigFileInDir(configFile, process.cwd()));
            // scan each source dir to find a valid config file
            this.opt.src.forEach((dir) => {
                Object.assign(config, this.findConfigFileInDir(configFile, dir));
            });
        });
        if ((0, lodash_1.isEqual)(config, defaultConfig)) {
            this.log.warn('No config files found.');
        }
        return config;
    }
    /**
     * Get json.header / json.footer title and markdown content
     *
     * Retrieves and processes header and footer configurations by reading content from the specified files
     * and rendering it with a Markdown parser if available.
     *
     * @param config - Configuration object containing details about header and footer settings.
     * @returns Object containing processed header and footer content, including their titles.
     * @throws If the header or footer file cannot be read.
     */
    getHeaderFooter(config) {
        const result = {};
        ['header', 'footer'].forEach((key) => {
            const configSection = config[key];
            if (configSection) {
                // Handle case where only title/icon are configured without filename
                if (!configSection.filename) {
                    if (configSection.title || configSection.icon) {
                        result[key] = {
                            title: configSection.title,
                            content: null, // No content if no file
                            icon: configSection.icon,
                        };
                    }
                    return; // Skip file processing
                }
                // Handle case with filename
                this.log.debug('Now looking for ' + key);
                // note that markdown files path is taken from first input value
                let filePath = path.join(config.input ? config.input[0] : './', configSection.filename);
                // try again to find it in current dir
                if (!fs.existsSync(filePath)) {
                    filePath = path.join(process.cwd(), configSection.filename);
                }
                // try again to find it in input folders
                if (!fs.existsSync(filePath)) {
                    filePath = this.findFileInSrc(configSection.filename);
                }
                // try again to find it in dir with the config file
                if (!fs.existsSync(filePath) && typeof this.opt.config === 'string') {
                    filePath = path.join(path.dirname(this.opt.config), configSection.filename);
                }
                try {
                    this.log.debug(`Reading ${key} file: ${filePath}`);
                    const content = fs.readFileSync(filePath, 'utf8');
                    result[key] = {
                        title: configSection.title,
                        content: this.app.markdownParser ? this.app.markdownParser.render(content) : content,
                        icon: configSection.icon, // Include icon if specified
                    };
                }
                catch (e) {
                    throw new Error('Can not read: ' + filePath);
                }
            }
        });
        return result;
    }
    /**
     * Search for a configuration file in a specified directory.
     *
     * @param filename - Name of the configuration file to locate.
     * @param dir - Directory to scan
     * @returns Configuration data if the file is found, or an empty object if not found.
     */
    findConfigFileInDir(filename, dir) {
        let foundConfig;
        const target = path.resolve(path.join(dir, filename));
        if (fs.existsSync(target)) {
            this.log.debug(`Found file: ${target}`);
            foundConfig = require(target);
            // if it has an apidoc key, read that
            if (foundConfig.apidoc) {
                this.log.verbose(`Using apidoc key of ${filename}`);
                // pull any missing config from root
                ['version', 'name', 'description'].forEach((key) => {
                    if (!foundConfig.apidoc[key] && foundConfig[key]) {
                        this.log.verbose(`Using ${key} from root of ${filename}`);
                        foundConfig.apidoc[key] = foundConfig[key];
                    }
                });
                return foundConfig.apidoc;
            }
            // for package.json we don't want to read it if it has no apidoc key
            if (filename !== 'package.json') {
                return foundConfig;
            }
        }
        return {};
    }
    /**
     * Look for a file in each of the input folders.
     *
     * If multiple files with the same name exist in different directories, the method returns the first valid file it finds.
     *
     * @param filename - Name of the file to search for
     * @returns Resolved path of the found file, or an empty string if not.
     */
    findFileInSrc(filename) {
        // scan each source dir to find a valid config file
        // note that any file found here will supersede a previously found file
        for (const dir of this.opt.src) {
            const target = path.join(dir, filename);
            if (fs.existsSync(target)) {
                this.log.debug('Found file: ' + target);
                return path.resolve(target);
            }
        }
        return '';
    }
}
exports.Reader = Reader;
//# sourceMappingURL=reader.js.map