"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.mapGoals = void 0;
const configuration_1 = require("@atomist/automation-client/lib/configuration");
const GitHubRepoRef_1 = require("@atomist/automation-client/lib/operations/common/GitHubRepoRef");
const ProjectOperationCredentials_1 = require("@atomist/automation-client/lib/operations/common/ProjectOperationCredentials");
const httpClient_1 = require("@atomist/automation-client/lib/spi/http/httpClient");
const yaml = require("js-yaml");
const stringify = require("json-stringify-safe");
const _ = require("lodash");
const Cancel_1 = require("../../../api/goal/common/Cancel");
const Immaterial_1 = require("../../../api/goal/common/Immaterial");
const Locking_1 = require("../../../api/goal/common/Locking");
const Queue_1 = require("../../../api/goal/common/Queue");
const pushTestUtils_1 = require("../../../api/mapping/support/pushTestUtils");
const goalCaching_1 = require("../../goal/cache/goalCaching");
const skill_1 = require("../../goal/common/skill");
const action_1 = require("../../goal/container/action");
const container_1 = require("../../goal/container/container");
const execute_1 = require("../../goal/container/execute");
const array_1 = require("../../util/misc/array");
const mapPushTests_1 = require("./mapPushTests");
const resolvePlaceholder_1 = require("./resolvePlaceholder");
const util_1 = require("./util");
const MapContainer = async (goals, sdm, additionalGoals, goalMakers, additionalTests, extensionTests) => {
    if (!!goals.containers) {
        if (!goals.name) {
            throw new Error(`Property 'name' missing on container goal:\n${JSON.stringify(goals, undefined, 2)}`);
        }
        const containers = [];
        for (const gc of goals.containers) {
            containers.push(Object.assign(Object.assign({}, gc), { name: gc.name.replace(/ /g, "-"), test: !!gc.test ? await mapPushTests_1.mapTests(gc.test, additionalTests, extensionTests) : undefined }));
        }
        const g = container_1.container(goals.name, {
            callback: containerCallback(),
            containers,
            initContainers: goals.initContainers,
            volumes: array_1.toArray(goals.volumes),
            progressReporter: container_1.ContainerProgressReporter,
            input: goals.input,
            output: goals.output,
            parameters: goals.parameters,
            fulfillment: goals.fulfillment,
        });
        return g;
    }
    return undefined;
};
const MapExecute = async (goals) => {
    if (!!goals.execute) {
        if (!goals.name) {
            throw new Error(`Property 'name' missing on execute goal:\n${JSON.stringify(goals, undefined, 2)}`);
        }
        const g = goals.execute;
        return execute_1.execute(g.name, {
            cmd: g.command || g.cmd,
            args: array_1.toArray(g.args),
            secrets: g.secrets,
        });
    }
    return undefined;
};
const MapImmaterial = async (goals) => {
    if (goals.use === "immaterial") {
        return Immaterial_1.ImmaterialGoals.andLock().goals;
    }
    return undefined;
};
const MapLock = async (goals) => {
    if (goals.use === "lock") {
        return Locking_1.Locking;
    }
    return undefined;
};
const MapQueue = async (goals) => {
    if (goals.use === "queue") {
        return new Queue_1.Queue({
            fetch: goals.fetch,
            concurrent: goals.concurrent,
        });
    }
    return undefined;
};
const MapCancel = async (goals) => {
    if (goals.use === "cancel") {
        return new Cancel_1.Cancel({ goals: [], goalNames: array_1.toArray(goals.goals) });
    }
    return undefined;
};
const MapAdditional = async (goals, sdm, additionalGoals) => {
    if (!!additionalGoals[goals.use]) {
        return additionalGoals[goals.use];
    }
    return undefined;
};
const MapReferenced = async (goals, sdm, additionalGoals, goalMakers, additionalTests, extensionTests) => {
    const ref = goals.ref;
    if (!!ref && ref.includes("/")) {
        const parameters = goals.parameters || {};
        const referencedGoal = await mapReferencedGoal(sdm, ref, parameters);
        if (!!referencedGoal) {
            return mapGoals(sdm, _.merge({}, referencedGoal, (goals || {})), additionalGoals, goalMakers, additionalTests, extensionTests);
        }
    }
    return undefined;
};
const MapGoalMakers = async (goals, sdm, additionalGoals, goalMakers) => {
    const use = goals.use;
    if (!!use && !!goalMakers[use]) {
        const goalMaker = goalMakers[use];
        try {
            return goalMaker(sdm, (goals.parameters || {}));
        }
        catch (e) {
            e.message = `Failed to make goal using ${use}: ${e.message}`;
            throw e;
        }
    }
    return undefined;
};
const MapFulfillment = async (goals) => {
    const regexp = /([@a-zA-Z-_]*)\/([a-zA-Z-_]*)(?:\/([a-zA-Z-_]*))?@?([a-zA-Z-_0-9\.]*)/i;
    const use = goals.use;
    if (!!use) {
        const match = regexp.exec(use);
        if (!!match) {
            return skill_1.skill(match[3].replace(/_/g, " "), `${match[1]}/${match[2]}`, {
                uniqueName: goals.name || match[3],
                parameters: goals.parameters,
                input: goals.input,
                output: goals.output,
                secrets: goals.secrets,
            });
        }
    }
    return undefined;
};
const MapAction = async (goals) => {
    if (!!goals.action) {
        return action_1.action({
            name: goals.name,
            image: goals.action,
            input: goals.input,
            output: goals.output,
            parameters: goals.parameters,
            secrets: goals.secrets,
        });
    }
    return undefined;
};
const MapGoals = [
    MapContainer,
    MapExecute,
    MapImmaterial,
    MapLock,
    MapCancel,
    MapQueue,
    MapAdditional,
    MapGoalMakers,
    MapReferenced,
    MapFulfillment,
    MapAction,
];
async function mapGoals(sdm, goals, additionalGoals, goalMakers, additionalTests, extensionTests) {
    if (Array.isArray(goals)) {
        const newGoals = [];
        for (const g of array_1.toArray(goals)) {
            newGoals.push(await mapGoals(sdm, g, additionalGoals, goalMakers, additionalTests, extensionTests));
        }
        return newGoals;
    }
    else {
        let goal;
        for (const mapGoal of MapGoals) {
            goal = await mapGoal(goals, sdm, additionalGoals, goalMakers, additionalTests, extensionTests);
            if (!!goal) {
                if (!Array.isArray(goal)) {
                    addDetails(goal, goals);
                    // Container goal handle their own caching
                    if (!(goal instanceof container_1.Container)) {
                        addCaching(goal, goals);
                    }
                }
                return goal;
            }
        }
    }
    throw new Error(`Unable to construct goal from '${stringify(goals)}'`);
}
exports.mapGoals = mapGoals;
function addDetails(goal, goals) {
    goal.definition = _.cloneDeep(goal.definition);
    if (goals.approval !== undefined) {
        goal.definition.approvalRequired = goals.approval;
    }
    if (goals.preApproval !== undefined) {
        goal.definition.preApprovalRequired = goals.preApproval;
    }
    if (goals.retry !== undefined) {
        goal.definition.retryFeasible = goals.retry;
    }
    if (!!goals.descriptions) {
        const descriptions = goals.descriptions;
        goal.definition.canceledDescription = descriptions.canceled;
        goal.definition.completedDescription = descriptions.completed;
        goal.definition.failedDescription = descriptions.failed;
        goal.definition.plannedDescription = descriptions.planned;
        goal.definition.requestedDescription = descriptions.requested;
        goal.definition.stoppedDescription = descriptions.stopped;
        goal.definition.waitingForApprovalDescription = descriptions.waitingForApproval;
        goal.definition.waitingForPreApprovalDescription = descriptions.waitingForPreApproval;
        goal.definition.workingDescription = descriptions.inProcess;
    }
    return goal;
}
function addCaching(goal, goals) {
    if (!!(goals === null || goals === void 0 ? void 0 : goals.input)) {
        goal.withProjectListener(goalCaching_1.cacheRestore({ entries: array_1.toArray(goals.input) }));
    }
    if (!!(goals === null || goals === void 0 ? void 0 : goals.output)) {
        goal.withProjectListener(goalCaching_1.cachePut({ entries: array_1.toArray(goals.output) }));
    }
    return goal;
}
function containerCallback() {
    return async (r, p, g, e, ctx) => {
        const pli = Object.assign(Object.assign({}, ctx), { push: e.push, project: p });
        const containersToRemove = [];
        for (const gc of r.containers) {
            let test;
            if (Array.isArray(gc.test)) {
                test = pushTestUtils_1.allSatisfied(...gc.test);
            }
            else {
                test = gc.test;
            }
            if (!!test && !(await test.mapping(pli))) {
                containersToRemove.push(gc);
            }
        }
        const registration = Object.assign(Object.assign({}, r), { containers: r.containers.filter(c => !containersToRemove.includes(c)) });
        return resolvePlaceholderContainerSpecCallback(registration, p, g, e, ctx);
    };
}
async function mapReferencedGoal(sdm, goalRef, parameters) {
    var _a, _b, _c, _d, _e, _f, _g;
    const regexp = /([a-zA-Z-_]*)\/([a-zA-Z-_]*)(?:\/([a-zA-Z-_]*))?@?([a-zA-Z-_0-9\.]*)/i;
    const match = regexp.exec(goalRef);
    if (!match) {
        return undefined;
    }
    const owner = match[1];
    const repo = match[2];
    const goalName = match[3];
    const goalNames = !!goalName ? [goalName] : [repo, repo.replace(/-goal/, "")];
    const ref = match[4] || "master";
    // Check if we have a github token to authenticate our requests
    let token = ((_c = (_b = (_a = sdm.configuration) === null || _a === void 0 ? void 0 : _a.sdm) === null || _b === void 0 ? void 0 : _b.github) === null || _c === void 0 ? void 0 : _c.token) || ((_g = (_f = (_e = (_d = sdm.configuration) === null || _d === void 0 ? void 0 : _d.sdm) === null || _e === void 0 ? void 0 : _e.goal) === null || _f === void 0 ? void 0 : _f.yaml) === null || _g === void 0 ? void 0 : _g.token);
    if (!token) {
        const workspaceId = _.get(sdm.configuration, "workspaceIds[0]");
        if (!!workspaceId) {
            try {
                const creds = await sdm.configuration.sdm.credentialsResolver.eventHandlerCredentials({ graphClient: sdm.configuration.graphql.client.factory.create(workspaceId, sdm.configuration) }, GitHubRepoRef_1.GitHubRepoRef.from({
                    owner: undefined,
                    repo,
                }));
                if (!!creds && ProjectOperationCredentials_1.isTokenCredentials(creds)) {
                    token = creds.token;
                    _.set(sdm.configuration, "sdm.goal.yaml.token", token);
                }
            }
            catch (e) {
                // Intentionally ignore that error here
            }
        }
    }
    const url = `https://api.github.com/repos/${owner}/${repo}/contents/goal.yaml?ref=${ref}`;
    try {
        const cacheKey = `configuration.sdm.goal.definition.cache[${url}]`;
        const cachedDocuments = _.get(sdm, cacheKey);
        let documents;
        if (!!cachedDocuments) {
            documents = cachedDocuments;
        }
        else {
            const client = sdm.configuration.http.client.factory.create(url);
            const response = await client.exchange(url, {
                method: httpClient_1.HttpMethod.Get,
                headers: Object.assign({}, (!!token ? { Authorization: `Bearer ${token}` } : {})),
                retry: { retries: 0 },
            });
            const content = Buffer.from(response.body.content, "base64").toString();
            documents = yaml.safeLoadAll(content);
            _.set(sdm, cacheKey, documents);
        }
        for (const document of documents) {
            for (const key in document) {
                if (document.hasOwnProperty(key) && goalNames.includes(key)) {
                    const pdg = document[key];
                    await configuration_1.resolvePlaceholders(pdg, value => resolvePlaceholder_1.resolvePlaceholder(value, undefined, {}, parameters, false));
                    return util_1.camelCase(pdg);
                }
            }
        }
    }
    catch (e) {
        throw new Error(`Referenced goal '${goalRef}' can not be created: ${e.message}`);
    }
    return undefined;
}
async function resolvePlaceholderContainerSpecCallback(r, p, g, e, ctx) {
    await configuration_1.resolvePlaceholders(r, value => resolvePlaceholder_1.resolvePlaceholder(value, e, ctx, ctx.parameters));
    return r;
}
//# sourceMappingURL=mapGoals.js.map