"use strict";
/*
 * Copyright © 2019 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.computeStartingPoint = exports.isSeedDrivenGeneratorParameters = exports.toGeneratorParametersMaker = exports.generatorCommand = void 0;
const HandlerResult_1 = require("@atomist/automation-client/lib/HandlerResult");
const onCommand_1 = require("@atomist/automation-client/lib/onCommand");
const RepoId_1 = require("@atomist/automation-client/lib/operations/common/RepoId");
const generatorUtils_1 = require("@atomist/automation-client/lib/operations/generate/generatorUtils");
const Project_1 = require("@atomist/automation-client/lib/project/Project");
const MessageClient_1 = require("@atomist/automation-client/lib/spi/message/MessageClient");
const constructionUtils_1 = require("@atomist/automation-client/lib/util/constructionUtils");
const slack_messages_1 = require("@atomist/slack-messages");
const storeGoals_1 = require("../../goal/storeGoals");
const handlerRegistrations_1 = require("../../machine/handlerRegistrations");
const projectLoaderRepoLoader_1 = require("../../machine/projectLoaderRepoLoader");
const toMachineOptions_1 = require("../../machine/toMachineOptions");
const messages_1 = require("../../misc/slack/messages");
const CachingProjectLoader_1 = require("../../project/CachingProjectLoader");
/**
 * Create a command handler for project generation
 * @param sdm this machine or its options
 * @param {EditorFactory<P extends SeedDrivenGeneratorParameters>} editorFactory to create editorCommand to perform transformation
 * @param {Maker<P extends SeedDrivenGeneratorParameters>} paramsMaker
 * @param {string} name
 * @param {Partial<GeneratorCommandDetails<P extends SeedDrivenGeneratorParameters>>} details
 * @return {HandleCommand}
 */
function generatorCommand(sdm, editorFactory, name, paramsMaker, fallbackTarget, startingPoint, details = {}, cr) {
    const detailsToUse = Object.assign(Object.assign({}, defaultDetails(toMachineOptions_1.toMachineOptions(sdm), name)), details);
    return onCommand_1.commandHandlerFrom(handleGenerate(editorFactory, detailsToUse, startingPoint, cr, toMachineOptions_1.toMachineOptions(sdm)), toGeneratorParametersMaker(paramsMaker, constructionUtils_1.toFactory(fallbackTarget)()), name, detailsToUse.description, detailsToUse.intent, detailsToUse.tags);
}
exports.generatorCommand = generatorCommand;
/**
 * Return a parameters maker that is targeting aware
 * @param {Maker<PARAMS>} paramsMaker
 * @return {Maker<EditorOrReviewerParameters & PARAMS>}
 */
function toGeneratorParametersMaker(paramsMaker, target) {
    const sampleParams = constructionUtils_1.toFactory(paramsMaker)();
    return isSeedDrivenGeneratorParameters(sampleParams) ?
        paramsMaker :
        () => {
            // This way we won't bother with source, but rely on startingPoint
            const rawParms = constructionUtils_1.toFactory(paramsMaker)();
            const allParms = rawParms;
            allParms.target = target;
            return allParms;
        };
}
exports.toGeneratorParametersMaker = toGeneratorParametersMaker;
function isSeedDrivenGeneratorParameters(p) {
    const maybe = p;
    return !!maybe && !!maybe.target;
}
exports.isSeedDrivenGeneratorParameters = isSeedDrivenGeneratorParameters;
function handleGenerate(editorFactory, details, startingPoint, cr, sdmo) {
    return (ctx, parameters) => {
        return handle(ctx, editorFactory, parameters, details, startingPoint, cr, sdmo);
    };
}
async function handle(ctx, editorFactory, params, details, startingPoint, cr, sdmo) {
    try {
        const pi = Object.assign(Object.assign({}, handlerRegistrations_1.toCommandListenerInvocation(cr, ctx, params, sdmo)), params);
        pi.credentials = await handlerRegistrations_1.resolveCredentialsPromise(pi.credentials);
        const r = await generatorUtils_1.generate(computeStartingPoint(params, ctx, details.repoLoader(params), details, startingPoint, cr, sdmo), ctx, pi.credentials, editorFactory(params, ctx), details.projectPersister, params.target.repoRef, params, undefined);
        if (!!cr.afterAction && r.success === true) {
            const afterActions = Array.isArray(cr.afterAction) ? cr.afterAction : [cr.afterAction];
            for (const afterAction of afterActions) {
                await afterAction(r.target, pi);
            }
        }
        // TODO cd support other providers which needs to start upstream from this
        if (params.target.repoRef.providerType === RepoId_1.ProviderType.github_com && r.success === true) {
            const repoProvenance = {
                repo: {
                    name: params.target.repoRef.repo,
                    owner: params.target.repoRef.owner,
                    providerId: "zjlmxjzwhurspem",
                },
                provenance: storeGoals_1.constructProvenance(ctx),
            };
            await ctx.messageClient.send(repoProvenance, MessageClient_1.addressEvent("SdmRepoProvenance"));
        }
        await ctx.messageClient.respond(messages_1.slackSuccessMessage(`Create Project`, `Successfully created new project ${slack_messages_1.bold(`${params.target.repoRef.owner}/${params.target.repoRef.repo}`)} at ${slack_messages_1.url(params.target.repoRef.url)}`));
        return {
            code: 0,
            // Redirect to local project page
            redirect: details.redirecter(params.target.repoRef),
            // local SDM uses this to print instructions
            generatedRepositoryUrl: params.target.repoRef.url,
        };
    }
    catch (err) {
        if (err instanceof handlerRegistrations_1.CommandListenerExecutionInterruptError) {
            // We're continuing
            return HandlerResult_1.Success;
        }
        await ctx.messageClient.respond(messages_1.slackErrorMessage(`Create Project`, `Project creation for ${slack_messages_1.bold(`${params.target.repoRef.owner}/${params.target.repoRef.repo}`)} failed:
${slack_messages_1.codeBlock(err.message)}`, ctx));
    }
    return undefined;
}
/**
 * Retrieve a seed. Set the seed location on the parameters if possible and necessary.
 */
async function computeStartingPoint(params, ctx, repoLoader, details, startingPoint, cr, sdmo) {
    if (!startingPoint) {
        if (!params.source || !params.source.repoRef) {
            throw new Error("If startingPoint is not provided in GeneratorRegistration, parameters.source must specify seed project location: " +
                `Offending registration had intent ${details.intent}`);
        }
        await infoMessage(`Cloning seed project from parameters ${slack_messages_1.url(params.source.repoRef.url)}`, ctx);
        return repoLoader(params.source.repoRef);
    }
    if (Project_1.isProject(startingPoint)) {
        await infoMessage(`Using starting point project specified in registration`, ctx);
        return startingPoint;
    }
    else if (RepoId_1.isRemoteRepoRef(startingPoint)) {
        const source = startingPoint;
        await infoMessage(`Cloning seed project from starting point ${slack_messages_1.bold(`${source.owner}/${source.repo}`)} at ${slack_messages_1.url(source.url)}`, ctx);
        const repoRef = startingPoint;
        params.source = { repoRef };
        return repoLoader(repoRef);
    }
    else {
        // Combine this for backward compatibility
        const pi = Object.assign(Object.assign({}, handlerRegistrations_1.toCommandListenerInvocation(cr, ctx, params, sdmo)), params);
        pi.credentials = await handlerRegistrations_1.resolveCredentialsPromise(pi.credentials);
        // It's a function that takes the parameters and returns either a project or a RemoteRepoRef
        const rr = startingPoint(pi);
        if (isProjectPromise(rr)) {
            const p = await rr;
            await infoMessage(`Using dynamically chosen starting point project ${slack_messages_1.bold(`${p.id.owner}:${p.id.repo}`)}`, ctx);
            return p;
        }
        if (Project_1.isProject(rr)) {
            await infoMessage(`Using dynamically chosen starting point project ${slack_messages_1.bold(`${rr.id.owner}:${rr.id.repo}`)}`, ctx);
            // params.source will remain undefined in this case
            return rr;
        }
        else {
            await infoMessage(`Cloning dynamically chosen starting point from ${slack_messages_1.url(rr.url)}`, ctx);
            params.source = { repoRef: rr };
            return repoLoader(rr);
        }
    }
}
exports.computeStartingPoint = computeStartingPoint;
function isProjectPromise(a) {
    return !!a.then;
}
function defaultDetails(opts, name) {
    return {
        description: name,
        repoFinder: opts.repoFinder,
        repoLoader: (p) => projectLoaderRepoLoader_1.projectLoaderRepoLoader(opts.projectLoader || new CachingProjectLoader_1.CachingProjectLoader(), p.target.credentials, true),
        projectPersister: opts.projectPersister,
        redirecter: () => undefined,
    };
}
async function infoMessage(text, ctx) {
    return ctx.messageClient.respond(messages_1.slackInfoMessage("Create Project", text));
}
//# sourceMappingURL=generatorCommand.js.map