"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigLoader = void 0;
const tslib_1 = require("tslib");
const _a = tslib_1.__importStar(require("@zyno-io/ts-reflection/type-metadata-runtime"));
const reflection_1 = require("../reflection");
const config_1 = require("@zyno-io/config");
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const env_1 = require("../env");
const const_1 = require("./const");
const __tsf_metadata_type = _a.createCompactMetadataRegistryV1("[1,[{\"kind\":2,\"typeName\":\"C\"},{\"kind\":18,\"typeName\":\"Record\",\"utilityType\":\"Record\",\"typeArguments\":[{\"kind\":6},{\"kind\":6}],\"index\":{\"kind\":6},\"types\":[]},{\"kind\":2,\"typeName\":\"ClassType\",\"typeArguments\":[{\"kind\":2,\"typeName\":\"C\"}]},{\"kind\":18,\"typeName\":\"Partial\",\"utilityType\":\"Partial\",\"typeArguments\":[{\"kind\":2,\"typeName\":\"C\"}],\"utilityKeys\":[],\"types\":[]}]]", []);
let _ConfigLoaderMetadata_1;
class ConfigLoader {
    configClass;
    defaultConfig;
    envPassthrough;
    constructor(configClass, defaultConfig, envPassthrough = []) {
        this.configClass = configClass;
        this.defaultConfig = defaultConfig;
        this.envPassthrough = envPassthrough;
    }
    load() {
        if (!env_1.Env.APP_ENV) {
            if ((0, const_1.isTestEnvironment)()) {
                env_1.Env.APP_ENV = 'test';
            }
            else if (env_1.Env.NODE_ENV !== 'production') {
                env_1.Env.APP_ENV = 'development';
            }
            else {
                throw new Error('APP_ENV must be specified in the environment');
            }
        }
        const config = new this.configClass();
        if (this.defaultConfig)
            Object.assign(config, this.defaultConfig);
        const loaded = this.loadConfigObject();
        const reflection = reflection_1.ReflectionClass.from(this.configClass);
        for (const prop of reflection.getProperties()) {
            const key = String(prop.name);
            if (key in loaded) {
                config[key] = coerceConfigValue(loaded[key], prop.getType());
            }
        }
        const errors = (0, reflection_1.validate)(config, this.configClass);
        if (errors.length) {
            throw new Error(`Invalid configuration: ${errors.map(e => `${e.path}: ${e.message}`).join(', ')}`);
        }
        this.loadMatchingValuesIntoProcessEnv(loaded);
        return config;
    }
    loadMatchingValuesIntoProcessEnv(loaded) {
        for (const [key, value] of Object.entries(loaded)) {
            if (this.envPassthrough.some(pattern => matchesEnvironmentKey(pattern, key))) {
                process.env[key] = value;
            }
        }
    }
    loadConfigObject() {
        const cwd = process.cwd();
        const configFiles = [
            '.env',
            '.env.local',
            ...((0, const_1.isTestEnvironment)() ? ['.env.development', '.env.development.local'] : []),
            `.env.${env_1.Env.APP_ENV}`,
            `.env.${env_1.Env.APP_ENV}.local`
        ];
        const paths = [];
        for (const file of configFiles) {
            const path = (0, node_path_1.resolve)(cwd, file);
            if ((0, node_fs_1.existsSync)(path))
                paths.push(path);
        }
        return (0, config_1.loadConfig)({
            file: paths
        });
    }
    static __tsfType = _ConfigLoaderMetadata_1 = _a.decodeCompactMetadataV1("[1,{\"kind\":16,\"name\":\"ConfigLoader\",\"typeName\":\"ConfigLoader\",\"classType\":{\"$tsf\":0},\"properties\":[],\"methods\":[{\"name\":\"load\",\"parameters\":[],\"returnType\":{\"$tsfType\":0}},{\"name\":\"loadConfigObject\",\"parameters\":[],\"returnType\":{\"$tsfType\":1}}],\"hasConstructor\":true,\"constructorParameters\":[{\"name\":\"configClass\",\"type\":{\"$tsfType\":2},\"optional\":false,\"default\":false},{\"name\":\"defaultConfig\",\"type\":{\"$tsfType\":3},\"optional\":true,\"default\":false},{\"name\":\"envPassthrough\",\"type\":{\"kind\":14,\"type\":{\"kind\":16,\"typeName\":\"RegExp\",\"classType\":{\"$tsf\":1}}},\"optional\":true,\"default\":true}]}]", [() => ConfigLoader, () => (typeof RegExp !== "undefined" ? RegExp : (typeof exports !== "undefined" ? exports.RegExp : undefined))], __tsf_metadata_type);
}
exports.ConfigLoader = ConfigLoader;
_ConfigLoaderMetadata_1.classType = ConfigLoader;
ConfigLoader.__tsfType = _ConfigLoaderMetadata_1;
function matchesEnvironmentKey(pattern, key) {
    return new RegExp(pattern.source, pattern.flags).test(key);
}
function coerceConfigValue(value, type) {
    const concrete = unwrapOptional(type);
    if (concrete.kind === reflection_1.ReflectionKind.number)
        return Number(value);
    if (concrete.kind === reflection_1.ReflectionKind.boolean)
        return value === '1' || value === 'true';
    if (concrete.kind === reflection_1.ReflectionKind.literal) {
        if (typeof concrete.literal === 'number')
            return Number(value);
        if (typeof concrete.literal === 'boolean')
            return value === '1' || value === 'true';
    }
    return value;
}
function unwrapOptional(type) {
    if (type.kind !== reflection_1.ReflectionKind.union)
        return type;
    const concrete = type.types.find(t => t.kind !== reflection_1.ReflectionKind.undefined && t.kind !== reflection_1.ReflectionKind.null);
    return concrete ?? type;
}
//# sourceMappingURL=config-loader.js.map