/**
 * Copyright (c) 2020-present, Goldman Sachs
 *
 * 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.
 */
import { action, computed, flow, makeObservable, observable } from 'mobx';
import { MINIMUM_SERVICE_OWNERS } from '../../../editor-state/element-editor-state/service/ServiceEditorState.js';
import { assertErrorThrown, LogEvent, ActionState, prettyCONSTName, assertNonEmptyString, guaranteeNonNullable, UnsupportedOperationError, assertTrue, URL_SEPARATOR, filterByType, compareSemVerVersions, } from '@finos/legend-shared';
import { LEGEND_STUDIO_APP_EVENT } from '../../../../../__lib__/LegendStudioEvent.js';
import { ServiceExecutionMode, buildLambdaVariableExpressions, VariableExpression, generateMultiplicityString, areMultiplicitiesEqual, Multiplicity, } from '@finos/legend-graph';
import { ServiceRegistrationEnvironmentConfig } from '../../../../../application/LegendStudioApplicationConfig.js';
import { ActionAlertActionType, ActionAlertType, } from '@finos/legend-application';
import { MASTER_SNAPSHOT_ALIAS } from '@finos/legend-server-depot';
export const LATEST_PROJECT_REVISION = 'Latest Project Revision';
export const PROJECT_SEMANTIC_VERSION_PATTERN = /^[0-9]*.[0-9]*.[0-9]*$/;
export const generateServiceManagementUrl = (baseUrl, serviceUrlPattern) => `${baseUrl}${URL_SEPARATOR +
    encodeURIComponent(serviceUrlPattern[0] === URL_SEPARATOR
        ? serviceUrlPattern.substring(1)
        : serviceUrlPattern)}`;
const getServiceExecutionMode = (mode) => {
    switch (mode) {
        case ServiceExecutionMode.FULL_INTERACTIVE:
            return ServiceExecutionMode.FULL_INTERACTIVE;
        case ServiceExecutionMode.SEMI_INTERACTIVE:
            return ServiceExecutionMode.SEMI_INTERACTIVE;
        case ServiceExecutionMode.PROD:
            return ServiceExecutionMode.PROD;
        default:
            throw new UnsupportedOperationError(`Encountered unsupported service execution mode '${mode}'`);
    }
};
export class ServiceConfigState {
    editorStore;
    registrationOptions = [];
    registrationState = ActionState.create();
    serviceEnv;
    serviceExecutionMode;
    projectVersion;
    enableModesWithVersioning;
    TEMPORARY__useStoreModel = false;
    TEMPORARY__useGenerateLineage = true;
    TEMPORARY__useGenerateOpenApi = false;
    constructor(editorStore, registrationOptions, enableModesWithVersioning) {
        makeObservable(this, {
            serviceEnv: observable,
            serviceExecutionMode: observable,
            projectVersion: observable,
            enableModesWithVersioning: observable,
            TEMPORARY__useStoreModel: observable,
            TEMPORARY__useGenerateLineage: observable,
            TEMPORARY__useGenerateOpenApi: observable,
            executionModes: computed,
            options: computed,
            versionOptions: computed,
            setServiceEnv: action,
            setServiceExecutionMode: action,
            setProjectVersion: action,
            setUseStoreModelWithFullInteractive: action,
            initialize: action,
            updateVersion: action,
            updateType: action,
            updateEnv: action,
        });
        this.editorStore = editorStore;
        this.registrationOptions = registrationOptions;
        this.enableModesWithVersioning = enableModesWithVersioning;
        this.registrationState.setMessageFormatter(prettyCONSTName);
        this.initialize();
    }
    get options() {
        if (this.enableModesWithVersioning) {
            return this.registrationOptions;
        }
        return this.registrationOptions
            .map((_envConfig) => {
            const envConfig = new ServiceRegistrationEnvironmentConfig();
            envConfig.env = _envConfig.env;
            envConfig.executionUrl = _envConfig.executionUrl;
            envConfig.managementUrl = _envConfig.managementUrl;
            // NOTE: For projects that we cannot create a version for, only fully-interactive mode is supported
            envConfig.modes = _envConfig.modes.filter((mode) => mode === ServiceExecutionMode.FULL_INTERACTIVE);
            return envConfig;
        })
            .filter((envConfig) => envConfig.modes.length);
    }
    get executionModes() {
        return (this.options.find((e) => e.env === this.serviceEnv)?.modes ?? []).map(getServiceExecutionMode);
    }
    get versionOptions() {
        if (this.enableModesWithVersioning &&
            this.serviceExecutionMode !== ServiceExecutionMode.FULL_INTERACTIVE) {
            const semanticVersions = this.editorStore.sdlcState.projectPublishedVersions
                .filter((version) => version.match(PROJECT_SEMANTIC_VERSION_PATTERN))
                .sort((v1, v2) => compareSemVerVersions(v2, v1));
            const snapshotVersions = this.editorStore.sdlcState.projectPublishedVersions
                .filter((version) => !version.match(PROJECT_SEMANTIC_VERSION_PATTERN))
                .sort((v1, v2) => v1.localeCompare(v2));
            const options = snapshotVersions
                .concat(semanticVersions)
                .map((version) => ({
                label: version,
                value: version,
            }));
            if (this.serviceEnv && this.serviceEnv.toUpperCase() === 'PROD') {
                // NOTE: we disallow registering against snapshot versions in PROD
                return semanticVersions.map((version) => ({
                    label: version,
                    value: version,
                }));
            }
            else {
                if (this.serviceExecutionMode !== ServiceExecutionMode.PROD) {
                    return [
                        {
                            label: LATEST_PROJECT_REVISION,
                            value: MASTER_SNAPSHOT_ALIAS,
                        },
                        ...options.filter((option) => option.value !== MASTER_SNAPSHOT_ALIAS),
                    ];
                }
            }
            return options;
        }
        return undefined;
    }
    setServiceEnv(val) {
        this.serviceEnv = val;
    }
    setServiceExecutionMode(val) {
        this.serviceExecutionMode = val;
    }
    setProjectVersion(val) {
        this.projectVersion = val;
    }
    setUseStoreModelWithFullInteractive(val) {
        this.TEMPORARY__useStoreModel = val;
    }
    setUseGenerateLineage(val) {
        this.TEMPORARY__useGenerateLineage = val;
    }
    setUseGenerateOpenApi(val) {
        this.TEMPORARY__useGenerateOpenApi = val;
    }
    initialize() {
        this.serviceEnv = this.registrationOptions[0]?.env;
        this.serviceExecutionMode = this.executionModes[0];
        this.updateVersion();
    }
    updateVersion() {
        if (this.serviceExecutionMode === ServiceExecutionMode.SEMI_INTERACTIVE) {
            this.projectVersion = MASTER_SNAPSHOT_ALIAS;
        }
        else if (this.serviceExecutionMode === ServiceExecutionMode.PROD) {
            this.projectVersion =
                this.editorStore.sdlcState.projectPublishedVersions[0];
        }
        else {
            this.projectVersion = undefined;
        }
    }
    updateType(val) {
        this.setServiceExecutionMode(val);
        this.updateVersion();
    }
    updateEnv(val) {
        this.setServiceEnv(val);
        this.setServiceExecutionMode(this.executionModes[0]);
    }
}
export class ServiceRegistrationState extends ServiceConfigState {
    service;
    activatePostRegistration = true;
    constructor(editorStore, service, registrationOptions, enableModesWithVersioning) {
        super(editorStore, registrationOptions, enableModesWithVersioning);
        makeObservable(this, {
            activatePostRegistration: observable,
            setActivatePostRegistration: action,
            registerService: flow,
        });
        this.service = service;
    }
    setActivatePostRegistration(val) {
        this.activatePostRegistration = val;
    }
    *registerService() {
        try {
            this.registrationState.inProgress();
            this.validateServiceForRegistration();
            const config = guaranteeNonNullable(this.options.find((info) => info.env === this.serviceEnv));
            const serverUrl = config.executionUrl;
            const projectConfig = guaranteeNonNullable(this.editorStore.projectConfigurationEditorState.projectConfiguration);
            this.registrationState.setMessage(`Registering service...`);
            const serviceRegistrationResult = (yield this.editorStore.graphManagerState.graphManager.registerService(this.service, this.editorStore.graphManagerState.graph, projectConfig.groupId, projectConfig.artifactId, this.projectVersion, serverUrl, guaranteeNonNullable(this.serviceExecutionMode), {
                TEMPORARY__useStoreModel: this.TEMPORARY__useStoreModel,
                TEMPORARY__useGenerateLineage: this.TEMPORARY__useGenerateLineage,
                TEMPORARY__useGenerateOpenApi: this.TEMPORARY__useGenerateOpenApi,
            }));
            if (this.activatePostRegistration) {
                this.registrationState.setMessage(`Activating service...`);
                yield this.editorStore.graphManagerState.graphManager.activateService(serverUrl, serviceRegistrationResult.serviceInstanceId);
            }
            assertNonEmptyString(serviceRegistrationResult.pattern, 'Service registration pattern is missing or empty');
            this.editorStore.applicationStore.alertService.setActionAlertInfo({
                message: `Service with pattern ${serviceRegistrationResult.pattern} registered ${this.activatePostRegistration ? 'and activated ' : ''}`,
                prompt: 'You can now launch and monitor the operation of your service',
                type: ActionAlertType.STANDARD,
                actions: [
                    {
                        label: 'Launch Service',
                        type: ActionAlertActionType.PROCEED,
                        handler: () => {
                            this.editorStore.applicationStore.navigationService.navigator.visitAddress(generateServiceManagementUrl(config.managementUrl, serviceRegistrationResult.pattern));
                        },
                        default: true,
                    },
                    {
                        label: 'Close',
                        type: ActionAlertActionType.PROCEED_WITH_CAUTION,
                    },
                ],
            });
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.SERVICE_REGISTRATION_FAILURE), error);
            this.editorStore.applicationStore.notificationService.notifyError(error);
        }
        finally {
            this.registrationState.reset();
            this.registrationState.setMessage(undefined);
        }
    }
    validateServiceForRegistration() {
        this.service.owners.forEach((owner) => assertNonEmptyString(owner, `Service can't have an empty owner name`));
        assertTrue(Boolean(this.service.ownership ??
            this.service.owners.length >= MINIMUM_SERVICE_OWNERS), `Service needs to have at least 2 owners in order to be registered`);
        guaranteeNonNullable(this.serviceEnv, 'Service registration environment can not be empty');
        guaranteeNonNullable(this.serviceExecutionMode, 'Service type can not be empty');
        if (this.serviceExecutionMode === ServiceExecutionMode.PROD ||
            this.serviceExecutionMode === ServiceExecutionMode.SEMI_INTERACTIVE) {
            guaranteeNonNullable(this.projectVersion, 'Service version can not be empty in Semi-interactive and Prod service type');
        }
        // validate service parameter multiplicities
        const SUPPORTED_SERVICE_PARAMETER_MULTIPLICITIES = [
            Multiplicity.ONE,
            Multiplicity.ZERO_MANY,
            Multiplicity.ZERO_ONE,
        ];
        const invalidParams = buildLambdaVariableExpressions(this.service.execution.func, this.editorStore.graphManagerState)
            .filter(filterByType(VariableExpression))
            .filter((p) => !SUPPORTED_SERVICE_PARAMETER_MULTIPLICITIES.some((m) => areMultiplicitiesEqual(m, p.multiplicity)));
        assertTrue(invalidParams.length === 0, `Parameter(s)${invalidParams.map((p) => ` ${p.name}: [${generateMultiplicityString(p.multiplicity.lowerBound, p.multiplicity.upperBound)}]`)} has/have unsupported multiplicity. Supported multiplicities include ${SUPPORTED_SERVICE_PARAMETER_MULTIPLICITIES.map((m) => ` [${generateMultiplicityString(m.lowerBound, m.upperBound)}]`)}.`);
    }
}
//# sourceMappingURL=ServiceRegistrationState.js.map