"use strict";
/*
 * Copyright © 2020 Atomist, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.configureYaml = exports.Target = void 0;
const configuration_1 = require("@atomist/automation-client/lib/configuration");
const changeCase = require("change-case");
const fg = require("fast-glob");
const fs = require("fs-extra");
const yaml = require("js-yaml");
const stringify = require("json-stringify-safe");
const _ = require("lodash");
const path = require("path");
const trace = require("stack-trace");
const github_1 = require("../../../pack/github-goal-status/github");
const goalState_1 = require("../../../pack/goal-state/goalState");
const array_1 = require("../../util/misc/array");
const configure_1 = require("../configure");
const mapCommand_1 = require("./mapCommand");
const mapGoals_1 = require("./mapGoals");
const mapRules_1 = require("./mapRules");
const util_1 = require("./util");
var Target;
(function (Target) {
    Target["SDM"] = "sdm";
    Target["Skill"] = "skill";
})(Target = exports.Target || (exports.Target = {}));
async function createExtensions(cwd, options, cfg, sdm) {
    var _a, _b, _c, _d, _e, _f;
    const commandCallback = async (c, k) => {
        let registration;
        try {
            const makerResult = await c(sdm);
            registration = Object.assign({ name: k }, makerResult);
        }
        catch (e) {
            e.message = `Failed to make command using CommandMaker ${k}: ${e.message}`;
            throw e;
        }
        try {
            sdm.addCommand(registration);
        }
        catch (e) {
            e.message = `Failed to add command ${k} '${stringify(registration)}': ${e.message}`;
            throw e;
        }
    };
    if (!((_a = options === null || options === void 0 ? void 0 : options.makers) === null || _a === void 0 ? void 0 : _a.commands)) {
        await awaitIterable(await requireCommands(cwd, (_b = options === null || options === void 0 ? void 0 : options.patterns) === null || _b === void 0 ? void 0 : _b.commands), commandCallback);
    }
    else {
        await awaitIterable(options.makers.commands, commandCallback);
    }
    const eventCallback = async (e, k) => {
        let registration;
        try {
            const makerResult = await e(sdm);
            registration = Object.assign({ name: k }, makerResult);
        }
        catch (e) {
            e.message = `Failed to make event using EventMaker ${k}: ${e.message}`;
            throw e;
        }
        try {
            sdm.addEvent(registration);
        }
        catch (e) {
            e.message = `Failed to add event ${k} '${stringify(registration)}': ${e.message}`;
            throw e;
        }
    };
    if (!((_c = options.makers) === null || _c === void 0 ? void 0 : _c.events)) {
        await awaitIterable(await requireEvents(cwd, (_d = options === null || options === void 0 ? void 0 : options.patterns) === null || _d === void 0 ? void 0 : _d.events), eventCallback);
    }
    else {
        await awaitIterable(options.makers.events, eventCallback);
    }
    await requireIngesters(cwd, (_e = options === null || options === void 0 ? void 0 : options.patterns) === null || _e === void 0 ? void 0 : _e.ingesters);
    sdm.addExtensionPacks(...(((_f = sdm.configuration.sdm) === null || _f === void 0 ? void 0 : _f.extensionPacks) || [
        goalState_1.goalStateSupport({
            cancellation: {
                enabled: true,
            },
        }),
        github_1.githubGoalStatusSupport(),
    ]));
}
/**
 * Load one or more yaml files to create goal sets
 *
 * When providing more than one yaml file, files are being loaded
 * in provided order with later files overwriting earlier ones.
 */
async function configureYaml(patterns, options = {}) {
    // Get the caller of this function to determine the cwd for resolving glob patterns
    const callerCallSite = trace
        .get()
        .filter(t => t.getFileName() !== __filename)
        .filter(t => !!t.getFileName())[0];
    const cwd = options.cwd || path.dirname(callerCallSite.getFileName());
    const cfg = await createConfiguration(cwd, options);
    return configure_1.configure(async (sdm) => {
        let sdmToUse = sdm;
        if (options.target === Target.Skill) {
            sdmToUse = mapCommand_1.decorateSoftwareDeliveryMachine(sdm);
        }
        await createExtensions(cwd, options, cfg, sdmToUse);
        return createGoalData(patterns, cwd, options, cfg, sdmToUse);
    }, options.options || {});
}
exports.configureYaml = configureYaml;
async function createConfiguration(cwd, options) {
    var _a, _b;
    const cfg = {};
    if (!((_a = options === null || options === void 0 ? void 0 : options.makers) === null || _a === void 0 ? void 0 : _a.configurations)) {
        await awaitIterable(await requireConfiguration(cwd, (_b = options === null || options === void 0 ? void 0 : options.patterns) === null || _b === void 0 ? void 0 : _b.configurations), async (v) => {
            const c = await v(cfg);
            configuration_1.deepMergeConfigs(cfg, c);
        });
    }
    else {
        await awaitIterable(options.makers.configurations, async (v) => {
            const c = await v(cfg);
            configuration_1.deepMergeConfigs(cfg, c);
        });
    }
    _.update(options, "options.preProcessors", old => (!!old ? old : []));
    options.options.preProcessors = [
        async (c) => configuration_1.deepMergeConfigs(c, cfg),
        ...array_1.toArray(options.options.preProcessors),
    ];
    return cfg;
}
// tslint:disable-next-line:cyclomatic-complexity
async function createGoalData(patterns, cwd, options, cfg, sdm) {
    var _a, _b;
    const additionalGoals = options.goals ? await sdm.createGoals(options.goals, options.configurers) : {};
    const goalMakers = !!((_a = options.makers) === null || _a === void 0 ? void 0 : _a.goals)
        ? options.makers.goals
        : await requireGoals(cwd, _.get(cfg, "extensions.goals"));
    const testMakers = !!((_b = options.makers) === null || _b === void 0 ? void 0 : _b.tests)
        ? options.makers.tests
        : await requireTests(cwd, _.get(cfg, "extensions.tests"));
    const files = await resolvePaths(cwd, patterns, true);
    const goalData = {};
    for (const file of files) {
        const configs = yaml.safeLoadAll(await fs.readFile(path.join(cwd, file), { encoding: "UTF-8" }));
        for (const config of configs) {
            if (!!config.configuration) {
                _.merge(sdm.configuration, util_1.camelCase(config.configuration));
            }
            if (!!config.skill) {
                _.merge(sdm.configuration, util_1.camelCase(config.skill));
                sdm.configuration.name = config.skill.name;
                sdm.name = config.skill.title || config.skill.name;
            }
            for (const k in config) {
                if (config.hasOwnProperty(k)) {
                    const value = config[k];
                    // Ignore two special keys used to set up the SDM
                    if (k === "name" || k === "configuration" || k === "skill") {
                        continue;
                    }
                    // Just create goals and register with SDM
                    if (k === "goals") {
                        await mapGoals_1.mapGoals(sdm, util_1.camelCase(value), additionalGoals, goalMakers, options.tests || {}, testMakers);
                    }
                    if (k === "rules") {
                        await mapRules_1.mapRules(value, goalData, sdm, options, additionalGoals, goalMakers, testMakers);
                    }
                }
            }
        }
    }
    return goalData;
}
async function requireExtensions(cwd, pattern, cb = () => { }) {
    if (pattern.length === 0) {
        return {};
    }
    const extensions = {};
    const files = await resolvePaths(cwd, pattern);
    for (const file of files) {
        const testJs = require(`${cwd}/${file}`);
        _.forEach(testJs, (v, k) => {
            if (!!cb) {
                cb(v, k, extensions);
            }
            extensions[k] = v;
        });
    }
    return extensions;
}
async function requireTests(cwd, pattern = ["tests/**.js", "lib/tests/**.js"]) {
    return requireExtensions(cwd, pattern, (v, k, e) => (e[changeCase.snake(k)] = v));
}
async function requireGoals(cwd, pattern = ["goals/**.js", "lib/goals/**.js"]) {
    return requireExtensions(cwd, pattern, (v, k, e) => (e[changeCase.snake(k)] = v));
}
async function requireCommands(cwd, pattern = ["commands/**.js", "lib/commands/**.js"]) {
    return requireExtensions(cwd, pattern);
}
async function requireEvents(cwd, pattern = ["events/**.js", "lib/events/**.js"]) {
    return requireExtensions(cwd, pattern);
}
async function requireConfiguration(cwd, pattern = ["config.js", "lib/config.js"]) {
    return requireExtensions(cwd, pattern);
}
async function requireIngesters(cwd, pattern = ["ingesters/**.graphql", "lib/graphql/ingester/**.graphql"]) {
    const ingesters = [];
    const files = await resolvePaths(cwd, pattern);
    for (const file of files) {
        ingesters.push((await fs.readFile(file)).toString());
    }
    return ingesters;
}
async function awaitIterable(elems, cb) {
    for (const k in elems) {
        if (elems.hasOwnProperty(k)) {
            const v = elems[k];
            await cb(v, k);
        }
    }
}
async function resolvePaths(cwd, patterns, watch = false) {
    const paths = await fg(array_1.toArray(patterns), { ignore: [`**/{.git,node_modules}/**`], cwd });
    if (watch) {
        util_1.watchPaths(paths);
    }
    return paths;
}
//# sourceMappingURL=configureYaml.js.map