"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.mergeOptions = exports.getGoalDefinitionFrom = exports.goal = exports.GoalWithFulfillment = exports.FulfillableGoalWithRegistrationsAndListeners = exports.FulfillableGoalWithRegistrations = exports.FulfillableGoal = exports.isSideEffect = exports.isImplementation = void 0;
const configuration_1 = require("@atomist/automation-client/lib/configuration");
const _ = require("lodash");
const sdmGoal_1 = require("../../api-helper/goal/sdmGoal");
const logInterpreters_1 = require("../../api-helper/log/logInterpreters");
const Registerable_1 = require("../machine/Registerable");
const commonPushTests_1 = require("../mapping/support/commonPushTests");
const ServiceRegistration_1 = require("../registration/ServiceRegistration");
const createGoal_1 = require("./common/createGoal");
const Goal_1 = require("./Goal");
const GoalNameGenerator_1 = require("./GoalNameGenerator");
const environment_1 = require("./support/environment");
function isImplementation(f) {
    return !!f && !!f.goalExecutor && true;
}
exports.isImplementation = isImplementation;
function isSideEffect(f) {
    return !isImplementation(f);
}
exports.isSideEffect = isSideEffect;
/**
 * Goal that registers goal implementations, side effects and callbacks on the
 * current SDM. No additional registration with the SDM is needed.
 */
class FulfillableGoal extends Goal_1.GoalWithPrecondition {
    constructor(definitionOrGoal, ...dependsOn) {
        super(Goal_1.isGoalDefinition(definitionOrGoal) ? definitionOrGoal : definitionOrGoal.definition, ...dependsOn);
        this.definitionOrGoal = definitionOrGoal;
        this.fulfillments = [];
        this.callbacks = [];
        this.projectListeners = [];
        this.goalListeners = [];
        Registerable_1.registerRegistrable(this);
    }
    register(sdm) {
        this.sdm = sdm;
        this.fulfillments.forEach(f => this.registerFulfillment(f));
        this.callbacks.forEach(cb => this.registerCallback(cb));
        this.goalListeners.forEach(gl => sdm.addGoalExecutionListener(gl));
    }
    withProjectListener(listener) {
        this.projectListeners.push(listener);
        return this;
    }
    withExecutionListener(listener) {
        const wrappedListener = async (gi) => {
            if (gi.goalEvent.uniqueName === this.uniqueName) {
                return listener(gi);
            }
        };
        if (this.sdm) {
            this.sdm.addGoalExecutionListener(wrappedListener);
        }
        this.goalListeners.push(wrappedListener);
        return this;
    }
    withService(registration) {
        this.addFulfillmentCallback({
            goal: this,
            callback: async (goalEvent, repoContext) => {
                const service = await registration.service(goalEvent, repoContext);
                if (!!service) {
                    const data = sdmGoal_1.goalData(goalEvent);
                    const servicesData = {};
                    _.set(servicesData, `${ServiceRegistration_1.ServiceRegistrationGoalDataKey}.${registration.name}`, service);
                    goalEvent.data = JSON.stringify(_.merge(data, servicesData));
                }
                return goalEvent;
            },
        });
        return this;
    }
    addFulfillmentCallback(cb) {
        if (this.sdm) {
            this.registerCallback(cb);
        }
        this.callbacks.push(cb);
        return this;
    }
    addFulfillment(fulfillment) {
        if (this.sdm) {
            this.registerFulfillment(fulfillment);
        }
        this.fulfillments.push(fulfillment);
        return this;
    }
    registerFulfillment(fulfillment) {
        if (isImplementation(fulfillment)) {
            let goalExecutor = fulfillment.goalExecutor;
            // Wrap the ExecuteGoal instance with WaitRules if provided
            if (Goal_1.isGoalDefinition(this.definitionOrGoal) && !!this.definitionOrGoal.preCondition) {
                goalExecutor = createGoal_1.createPredicatedGoalExecutor(this.definitionOrGoal.uniqueName, goalExecutor, this.definitionOrGoal.preCondition);
            }
            if (Goal_1.isGoalDefinition(this.definitionOrGoal) && !!this.definitionOrGoal.retryCondition) {
                goalExecutor = createGoal_1.createRetryingGoalExecutor(this.definitionOrGoal.uniqueName, goalExecutor, this.definitionOrGoal.retryCondition);
            }
            this.sdm.addGoalImplementation(fulfillment.name, this, goalExecutor, {
                pushTest: fulfillment.pushTest || commonPushTests_1.AnyPush,
                progressReporter: fulfillment.progressReporter,
                logInterpreter: fulfillment.logInterpreter,
                projectListeners: this.projectListeners,
            });
        }
        else if (isSideEffect(fulfillment)) {
            this.sdm.addGoalSideEffect(this, fulfillment.name, fulfillment.registration, fulfillment.pushTest);
        }
    }
    async plan(pli, goals) {
        return undefined;
    }
    registerCallback(cb) {
        this.sdm.goalFulfillmentMapper.addFulfillmentCallback(cb);
    }
}
exports.FulfillableGoal = FulfillableGoal;
/**
 * Goal that accepts registrations of R.
 */
class FulfillableGoalWithRegistrations extends FulfillableGoal {
    constructor(definitionOrGoal, ...dependsOn) {
        super(definitionOrGoal, ...dependsOn);
        this.definitionOrGoal = definitionOrGoal;
        this.registrations = [];
    }
    with(registration) {
        this.registrations.push(registration);
        return this;
    }
}
exports.FulfillableGoalWithRegistrations = FulfillableGoalWithRegistrations;
/**
 * Goal that accepts registrations of R and listeners of L.
 */
class FulfillableGoalWithRegistrationsAndListeners extends FulfillableGoalWithRegistrations {
    constructor(definitionOrGoal, ...dependsOn) {
        super(definitionOrGoal, ...dependsOn);
        this.definitionOrGoal = definitionOrGoal;
        this.listeners = [];
    }
    withListener(listener) {
        this.listeners.push(listener);
        return this;
    }
}
exports.FulfillableGoalWithRegistrationsAndListeners = FulfillableGoalWithRegistrationsAndListeners;
/**
 * Generic goal that can be used with a GoalDefinition.
 * Register goal implementations or side effects to this goal instance.
 */
class GoalWithFulfillment extends FulfillableGoal {
    withCallback(cb) {
        this.addFulfillmentCallback(cb);
        return this;
    }
    with(fulfillment) {
        this.addFulfillment(fulfillment);
        return this;
    }
}
exports.GoalWithFulfillment = GoalWithFulfillment;
/**
 * Creates a new GoalWithFulfillment instance using conventions if overwrites aren't provided
 *
 * Call this from your machine.ts where you configure your sdm to create a custom goal.
 *
 * Caution: if you wrap this in another function, then you MUST provide details.uniqueName,
 *          because the default is based on where in the code this `goal` function is called.
 *
 * @param details It is highly recommended that you supply at least uniqueName.
 * @param goalExecutor
 * @param options
 */
function goal(details = {}, goalExecutor, options) {
    const def = getGoalDefinitionFrom(details, GoalNameGenerator_1.DefaultGoalNameGenerator.generateName(details.displayName || "goal"));
    const g = new GoalWithFulfillment(def);
    if (!!goalExecutor) {
        const optsToUse = Object.assign({ pushTest: commonPushTests_1.AnyPush, logInterpreter: logInterpreters_1.LogSuppressor }, (!!options ? options : {}));
        g.with(Object.assign({ name: def.uniqueName, goalExecutor }, optsToUse));
        if (!!optsToUse.plan) {
            g.plan = optsToUse.plan;
        }
    }
    return g;
}
exports.goal = goal;
/**
 * Construct a PredicatedGoalDefinition from the provided goalDetails
 * @param goalDetails
 * @param uniqueName
 * @param definition
 */
// tslint:disable:cyclomatic-complexity
function getGoalDefinitionFrom(goalDetails, uniqueName, definition) {
    if (typeof goalDetails === "string") {
        return Object.assign(Object.assign({}, (definition || {})), { uniqueName: goalDetails || uniqueName });
    }
    else {
        const defaultDefinition = Object.assign({}, (definition || {}));
        const goalDetailsToUse = goalDetails || {};
        if (!!goalDetailsToUse.descriptions) {
            defaultDefinition.canceledDescription = goalDetailsToUse.descriptions.canceled || defaultDefinition.canceledDescription;
            defaultDefinition.completedDescription = goalDetailsToUse.descriptions.completed || defaultDefinition.completedDescription;
            defaultDefinition.failedDescription = goalDetailsToUse.descriptions.failed || defaultDefinition.failedDescription;
            defaultDefinition.plannedDescription = goalDetailsToUse.descriptions.planned || defaultDefinition.plannedDescription;
            defaultDefinition.requestedDescription = goalDetailsToUse.descriptions.requested || defaultDefinition.requestedDescription;
            defaultDefinition.stoppedDescription = goalDetailsToUse.descriptions.stopped || defaultDefinition.stoppedDescription;
            defaultDefinition.waitingForApprovalDescription =
                goalDetailsToUse.descriptions.waitingForApproval || defaultDefinition.waitingForApprovalDescription;
            defaultDefinition.waitingForPreApprovalDescription =
                goalDetailsToUse.descriptions.waitingForPreApproval || defaultDefinition.waitingForPreApprovalDescription;
            defaultDefinition.workingDescription = goalDetailsToUse.descriptions.inProcess || defaultDefinition.workingDescription;
        }
        return Object.assign(Object.assign({}, defaultDefinition), { displayName: goalDetailsToUse.displayName || defaultDefinition.displayName, uniqueName: goalDetailsToUse.uniqueName || uniqueName, environment: getEnvironment(goalDetailsToUse), approvalRequired: goalDetailsToUse.approval || defaultDefinition.approvalRequired, preApprovalRequired: goalDetailsToUse.preApproval || defaultDefinition.preApprovalRequired, retryFeasible: goalDetailsToUse.retry || defaultDefinition.retryFeasible, isolated: goalDetailsToUse.isolate || defaultDefinition.isolated, preCondition: goalDetailsToUse.preCondition });
    }
}
exports.getGoalDefinitionFrom = getGoalDefinitionFrom;
/**
 * Merge Goal configuration options into a final options object.
 * Starts off by merging the explicitly provided options over the provided defaults; finally merges the configuration
 * values at the given configuration path (prefixed with sdm.) over the previous merge.
 * @param defaults
 * @param explicit
 * @param configurationPath
 */
function mergeOptions(defaults, explicit, configurationPath) {
    const options = _.merge(_.cloneDeep(defaults), explicit || {});
    if (!!configurationPath) {
        const configurationOptions = configuration_1.configurationValue(`sdm.${configurationPath}`, {});
        return _.merge(options, configurationOptions);
    }
    return options;
}
exports.mergeOptions = mergeOptions;
function getEnvironment(details) {
    if (details && details.environment && typeof details.environment === "string") {
        switch (details.environment) {
            case "testing":
                return environment_1.StagingEnvironment;
            case "production":
                return environment_1.ProductionEnvironment;
            default:
                return environment_1.IndependentOfEnvironment;
        }
    }
    else if (details && typeof details.environment !== "string") {
        return details.environment;
    }
    else {
        return environment_1.IndependentOfEnvironment;
    }
}
//# sourceMappingURL=GoalWithFulfillment.js.map