"use strict";
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Overlayer = exports.ConvertTo = exports.EnumCase = void 0;
const codiac_common_1 = require("@codiac.io/codiac-common");
const changeCase = require("change-case");
const _ = require("lodash");
/**
 * (Optional: defaults to "service")  Differentiator for various asset behaviors (eg: "job" or "helm").
 */
exports.EnumCase = {
    /** `"twoWords"` */
    camelCase: 'camelCase',
    /** `"Two Words"` */
    capitalCase: 'capitalCase',
    /** `"TWO_WORDS"` */
    constantCase: 'constantCase',
    /** `"two.words"` */
    dotCase: 'dotCase',
    /** `"two-words"` */
    kebabCase: 'kebabCase',
    /** `"two words"` */
    noCase: 'noCase',
    /** `"TwoWords"` */
    pascalCase: 'pascalCase',
    /** `"Two_Words"` */
    pascalSnakeCase: 'pascalSnakeCase',
    /** `"two/words"` */
    pathCase: 'pathCase',
    /** `"Two words"` */
    sentenceCase: 'sentenceCase',
    /** `"two_words"` */
    snakeCase: 'snakeCase',
    /** `"Two-Words"` */
    trainCase: 'trainCase'
};
class ConvertTo {
}
exports.ConvertTo = ConvertTo;
_a = exports.EnumCase.camelCase, _b = exports.EnumCase.capitalCase, _c = exports.EnumCase.constantCase, _d = exports.EnumCase.dotCase, _e = exports.EnumCase.kebabCase, _f = exports.EnumCase.noCase, _g = exports.EnumCase.pascalCase, _h = exports.EnumCase.pascalSnakeCase, _j = exports.EnumCase.pathCase, _k = exports.EnumCase.sentenceCase, _l = exports.EnumCase.snakeCase, _m = exports.EnumCase.trainCase;
/** `"twoWords"` */
ConvertTo[_a] = changeCase.camelCase;
/** `"Two Words"` */
ConvertTo[_b] = changeCase.capitalCase;
/** `"TWO_WORDS"` */
ConvertTo[_c] = changeCase.constantCase;
/** `"two.words"` */
ConvertTo[_d] = changeCase.dotCase;
/** `"two-words"` */
ConvertTo[_e] = changeCase.kebabCase;
/** `"two words"` */
ConvertTo[_f] = changeCase.noCase;
/** `"TwoWords"` */
ConvertTo[_g] = changeCase.pascalCase;
/** `"Two_Words"` */
ConvertTo[_h] = changeCase.pascalSnakeCase;
/** `"two/words"` */
ConvertTo[_j] = changeCase.pathCase;
/** `"Two words"` */
ConvertTo[_k] = changeCase.sentenceCase;
/** `"two_words"` */
ConvertTo[_l] = changeCase.snakeCase;
/** `"Two-Words"` */
ConvertTo[_m] = changeCase.trainCase;
class Overlayer {
    /** Returns a new version of the given object
     * by applying/overwriting property values
     * with values from env variables.
     * (DOES NOT mutate the original)
     *
     * @param target The target object to be overlaid with available env values.  (NOTE: The original object is NOT mutated).
     * @param targetPropertyNames (optional: defaults to all) The list of property names on the given object
     *  that are to be sought for in the process env (and overwritten if found).
     *  If none are provided, all env variables (matching the given `options.envPrefix`) will be applied to target object
     * @param options Behaviors to apply in the conversion
     * */
    static overlay(target, flatSource, options) {
        var _o, _p, _q, _r;
        const defaultMergeOptions = {
            targetPropertyNames: Object.keys(target),
            sourceCase: exports.EnumCase.constantCase,
            sourceHierarchyDelim: this.ENV_HIERARCHY_DELIM,
            sourcePrefix: undefined
        };
        const defaults = {
            sourceCase: exports.EnumCase.constantCase,
            sourceHierarchyDelim: this.ENV_HIERARCHY_DELIM,
            sourcePrefix: "",
            targetObjectCase: undefined
        };
        // const ultOptions = options.fromTarget ?? options.fromSource ?? options.explicit ?? defaultMergeOptions;
        // --------------
        // 1. Construct source object from env values
        // --------------
        /** Map of object property names to the env keys they'll be matched with. */
        let propertyMap = {};
        if (options === null || options === void 0 ? void 0 : options.fromTarget) {
            // --------------------------------------------
            // infer source keys from target properties
            // --------------------------------------------
            const flatTarget = codiac_common_1.ObjectFlattener.flatten(target);
            const targetKeys = flatTarget.map(kv => kv.key);
            targetKeys.push(...options.fromTarget.targetPropertyNames.filter(k => !targetKeys.includes(k)));
            for (let p of targetKeys) {
                const segments = p.split(".");
                if (options.fromTarget.sourceCase) {
                    for (let i = 0; i < segments.length; i++) {
                        const propertyName = segments[i];
                        segments[i] = ConvertTo[options.fromTarget.sourceCase](propertyName);
                    }
                }
                propertyMap[p] = ((_o = options.fromTarget.sourcePrefix) !== null && _o !== void 0 ? _o : "").concat(segments.join((_p = options.fromTarget.sourceHierarchyDelim) !== null && _p !== void 0 ? _p : defaults.sourceHierarchyDelim));
            }
        }
        else if (options === null || options === void 0 ? void 0 : options.fromSource) {
            // --------------------------------------------
            // Infer target properties from source keys
            // --------------------------------------------
            let srcKeys;
            if (options.fromSource.sourceKeys != undefined && !_.isEmpty(options.fromSource.sourceKeys)) {
                // take the given source keys
                srcKeys = options.fromSource.sourceKeys;
            }
            else {
                if (!_.isEmpty(options.fromSource.sourcePrefix)) {
                    // Take the envs with the given prefix
                    srcKeys = Object.keys(flatSource).filter(k => k.startsWith(options.fromSource.sourcePrefix));
                }
                else {
                    // use all the envs
                    srcKeys = Object.keys(flatSource);
                }
            }
            // Ultimate propertyMap
            for (let srcKey of srcKeys) {
                let objKey = srcKey;
                // Remove prefix
                if (options.fromSource.sourcePrefix && objKey.startsWith(options.fromSource.sourcePrefix)) {
                    objKey = objKey.replace(options.fromSource.sourcePrefix, ""); // <-- only replaces the first one
                }
                // Lettercase for each hierarchy segment
                let segments = objKey.split((_q = options.fromSource.sourceHierarchyDelim) !== null && _q !== void 0 ? _q : defaults.sourceHierarchyDelim);
                if (options.fromSource.targetObjectCase) {
                    for (let i = 0; i < segments.length; i++) {
                        const propertyName = segments[i];
                        segments[i] = ConvertTo[options.fromSource.targetObjectCase](propertyName);
                    }
                }
                objKey = segments.join("."); // dot notation for hierarchy
                propertyMap[objKey] = srcKey;
            }
        }
        else if (options === null || options === void 0 ? void 0 : options.explicit) {
            propertyMap = _.clone(options.explicit.propertyMap);
        }
        else {
            // infer source keys from target properties (using defaults)
            const optz = {
                prefixCharacters: defaults.sourcePrefix
                // delimiter: "."
            };
            for (let p of defaultMergeOptions.targetPropertyNames) {
                propertyMap[p] = defaultMergeOptions.sourceCase ? changeCase.constantCase(p, optz) : ((_r = defaultMergeOptions.sourcePrefix) !== null && _r !== void 0 ? _r : "").concat(p);
            }
        }
        // --------------
        // 2. Unflatten the source object
        // --------------
        let mappedFlat = {};
        for (let targetDotPath in propertyMap) {
            const srcProp = propertyMap[targetDotPath];
            // Overwrite keys that exist in the source object 
            // TODO: Consider ignoring if the source value is undefined or empty?
            if (Object.keys(flatSource).includes(srcProp)) {
                mappedFlat[targetDotPath] = flatSource[srcProp];
            }
        }
        const mappedSource = codiac_common_1.ObjectFlattener.unflatten(mappedFlat);
        // --------------
        // 3. Merge source into target
        // --------------
        const ult = _.assign({}, target, mappedSource);
        return ult;
    }
}
exports.Overlayer = Overlayer;
/** Delimiter to use in env value key names to denote object hierarchy,
 * for use when applying env values (as overrides) to the ultimate app config object.
 *
 * eg:
 * (eg: when `ENV_HIERARCHY_DELIM="__"`, the env variable `"DB__NAME=Jake"` would resolve to `{ "db": { "name": "Jake" } }` on the target object).
 */
Overlayer.ENV_HIERARCHY_DELIM = "__";
//# sourceMappingURL=overlayer.js.map