"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigFile = exports.findUIKit1Modules = exports.ResourceDefinitionMissingError = exports.InvalidManifestError = void 0;
const tslib_1 = require("tslib");
const yaml_1 = require("yaml");
const ui_1 = require("../ui");
const _1 = require("./");
const case_1 = tslib_1.__importDefault(require("case"));
const config_1 = require("./config");
const manifest_1 = require("@forge/manifest");
const url_1 = require("url");
const shared_1 = require("../shared");
class InvalidManifestError extends shared_1.ValidationError {
    constructor() {
        super(ui_1.Text.artifact.error.invalidYaml(_1.manifestFileName));
    }
}
exports.InvalidManifestError = InvalidManifestError;
class ResourceDefinitionMissingError extends shared_1.UserError {
    constructor(resources) {
        super(ui_1.Text.error.resourceDefinitionMissing(resources));
    }
}
exports.ResourceDefinitionMissingError = ResourceDefinitionMissingError;
const isUIKit1 = (module) => {
    return (typeof module === 'object' &&
        module !== null &&
        module.hasOwnProperty('function') &&
        !module.hasOwnProperty('render') &&
        !module.hasOwnProperty('resource'));
};
function findUIKit1Modules(modules) {
    const allUIKit1SupportedModules = [
        'confluence:contentAction',
        'confluence:contentBylineItem',
        'confluence:contextMenu',
        'confluence:customContent',
        'confluence:globalPage',
        'confluence:globalSettings',
        'confluence:homepageFeed',
        'macro',
        'confluence:spacePage',
        'confluence:spaceSettings',
        'jira:adminPage',
        'jira:customField',
        'jira:customFieldType',
        'jira:dashboardGadget',
        'jira:globalPage',
        'jira:issueAction',
        'jira:issueActivity',
        'jira:issueContext',
        'jira:issueGlance',
        'jira:issuePanel',
        'jira:projectPage',
        'jira:projectSettingsPage',
        'jiraServiceManagement:organizationPanel',
        'jiraServiceManagement:portalFooter',
        'jiraServiceManagement:portalHeader',
        'jiraServiceManagement:portalProfilePanel',
        'jiraServiceManagement:portalRequestCreatePropertyPanel',
        'jiraServiceManagement:portalRequestDetail',
        'jiraServiceManagement:portalRequestDetailPanel',
        'jiraServiceManagement:portalRequestViewAction',
        'jiraServiceManagement:portalSubheader',
        'jiraServiceManagement:portalUserMenuAction',
        'jiraServiceManagement:queuePage',
        'bitbucket:repoCodeOverviewCard',
        'bitbucket:repoCodeOverviewAction',
        'bitbucket:repoCodeOverviewPanel',
        'bitbucket:repoPullRequestCard',
        'bitbucket:repoPullRequestAction',
        'bitbucket:repoPullRequestOverviewPanel',
        'bitbucket:repoMainMenuPage',
        'bitbucket:repoSettingsMenuPage',
        'bitbucket:workspaceSettingsMenuPage',
        'compass:adminPage',
        'compass:componentPage',
        'compass:globalPage',
        'compass:teamPage'
    ];
    return (0, manifest_1.getValidModules)(modules)
        .filter((moduleType) => {
        return allUIKit1SupportedModules.includes(moduleType);
    })
        .map((moduleType) => {
        const moduleItems = modules[moduleType] || [];
        return moduleItems.filter(isUIKit1).map((module) => module.key);
    })
        .flat()
        .filter((a) => a);
}
exports.findUIKit1Modules = findUIKit1Modules;
class ConfigFile {
    fileReader;
    fileWriter;
    manifestParser;
    constructor(fileReader, fileWriter) {
        this.fileReader = fileReader;
        this.fileWriter = fileWriter;
        this.manifestParser = new manifest_1.ManifestParserBuilder().withInterpolators().build();
    }
    async getAppHandlers() {
        const config = await this.readConfig();
        const functions = config.modules?.function ?? [];
        const handlers = Array.from(new Set(functions.map((f) => f.handler)));
        return handlers.map((handler) => {
            const parts = handler.split('.');
            if (parts.length !== 2) {
                throw new shared_1.ValidationError(ui_1.Text.error.handlerFormat(handler));
            }
            const [module, func] = parts;
            return { module, func };
        });
    }
    async readConfig() {
        const manifestFileContents = this.fileReader.readFile(_1.manifestFileName);
        try {
            return this.manifestParser.parseManifest({
                content: manifestFileContents,
                filePath: _1.manifestFileName
            });
        }
        catch {
            throw new InvalidManifestError();
        }
    }
    async runtimeName() {
        const config = await this.readConfig();
        return config.app?.runtime?.name;
    }
    makeManifestUnique(modules, appName) {
        const actualModules = {};
        const skipAddingAppNameToModules = ['function', 'action'];
        for (const [moduleKey, moduleArray] of Object.entries(modules)) {
            actualModules[moduleKey] = moduleArray?.map((module) => {
                if (!skipAddingAppNameToModules.includes(moduleKey) && !moduleKey.startsWith(case_1.default.kebab(appName))) {
                    module['key'] = case_1.default.kebab([appName, module.key].join(' '));
                    if ('title' in module) {
                        module['title'] = appName;
                    }
                    if ('name' in module) {
                        module['name'] = appName;
                    }
                }
                return module;
            });
        }
        return actualModules;
    }
    async prependAppNameToModules(name) {
        const manifestFileContents = this.fileReader.readFile(_1.manifestFileName);
        const currentConfigDoc = (0, yaml_1.parseDocument)(manifestFileContents || '');
        const modules = currentConfigDoc.get('modules');
        if ((0, yaml_1.isMap)(modules)) {
            const modulesObject = modules.toJSON();
            const modulesWithUniqueName = this.makeManifestUnique(modulesObject, name);
            currentConfigDoc.set('modules', modulesWithUniqueName);
            this.writeManifest(currentConfigDoc);
        }
    }
    writeManifest(doc) {
        this.fileWriter.writeStringToFile(doc.toString({ nullStr: '' }), _1.manifestFileName);
    }
    async writeToConfigFile(configKey, config) {
        const manifestFileContents = this.fileReader.readFile(_1.manifestFileName);
        const currentConfigDoc = (0, yaml_1.parseDocument)(manifestFileContents || '');
        Object.keys(config).forEach((key) => {
            if (config[key] === undefined) {
                delete config[key];
            }
        });
        currentConfigDoc.set(configKey, config);
        this.writeManifest(currentConfigDoc);
    }
    getHostedResourceModules(modules) {
        const modulesArray = (0, shared_1.flat)(Object.values(modules));
        return modulesArray.filter(config_1.isHostedResourceModule);
    }
    assertNoMissingResources(modules, resources) {
        const modulesResourcesKeys = modules
            .flatMap((module) => {
            return [module.resource, ...config_1.ModuleEntryPoints.map((entryPoint) => module[entryPoint]?.resource || '')];
        })
            .filter(Boolean);
        const missingModuleResourcesKeys = modulesResourcesKeys.filter((key) => !resources.some((resource) => resource.key === key));
        if (missingModuleResourcesKeys.length > 0) {
            throw new ResourceDefinitionMissingError(missingModuleResourcesKeys);
        }
    }
    getRemoteFromEgressPermission(remoteEgressPermission, remotes) {
        if (!remotes) {
            return remoteEgressPermission;
        }
        for (const remote of remotes) {
            if (remote.key === remoteEgressPermission) {
                if (typeof remote.baseUrl === 'string') {
                    const baseUrl = remote.baseUrl;
                    return new url_1.URL(baseUrl).hostname;
                }
                else if (typeof remote.baseUrl == 'object') {
                    const baseUrl = remote.baseUrl.default;
                    return new url_1.URL(baseUrl).hostname;
                }
            }
        }
        return remoteEgressPermission;
    }
    instanceOfRemote(remote) {
        return typeof remote !== 'string' && 'remote' in remote;
    }
    async getEgressPermissions() {
        const { permissions, remotes } = await this.readConfig();
        const egressPermissions = permissions?.external;
        if (!egressPermissions) {
            return [];
        }
        const { fetch: fetchEgress, ...otherEgress } = egressPermissions;
        const getDomainsFromPermissions = (input, typePrefix = undefined) => Object.entries(input)
            .filter((entry) => Array.isArray(entry[1]))
            .map((entry) => ({
            type: typePrefix ? `${typePrefix}_${entry[0]}` : entry[0],
            domains: entry[1]?.map((url) => {
                const domain = this.instanceOfRemote(url) ? url.remote : url;
                try {
                    if (this.instanceOfRemote(url)) {
                        return this.getRemoteFromEgressPermission(url.remote, remotes);
                    }
                    return new url_1.URL(typeof url === 'object' ? url.address : url).hostname;
                }
                catch (e) {
                }
                return typeof domain === 'object' ? domain.address : domain;
            })
        }));
        const egressUrls = getDomainsFromPermissions(otherEgress);
        const fetchUrls = fetchEgress ? getDomainsFromPermissions(fetchEgress, 'fetch') : [];
        return [...egressUrls, ...fetchUrls];
    }
    async getAuthProviders() {
        const config = await this.readConfig();
        return (config.providers?.auth.reduce((hash, { key, name }) => {
            return Object.assign(hash, {
                [key]: {
                    key,
                    name
                }
            });
        }, {}) || {});
    }
    async getResources(resourceTypes) {
        const { modules, resources } = await this.readConfig();
        const hostedResourceModules = modules ? this.getHostedResourceModules(modules) : [];
        const maybeResources = resources || [];
        const validResources = maybeResources.filter(config_1.validateResource);
        this.assertNoMissingResources(hostedResourceModules, validResources);
        const validResourcesWithDetails = validResources.map((resource) => ({
            ...resource,
            resourceType: this.getResourceType(resource, hostedResourceModules)
        })) || [];
        if (!resourceTypes) {
            return validResourcesWithDetails;
        }
        return validResourcesWithDetails.filter(({ resourceType }) => resourceTypes.includes(resourceType));
    }
    async getConnectKey() {
        const { app } = await this.readConfig();
        return app.connect?.key;
    }
    getResourceType({ key: resourceKey }, allModules) {
        const linkedModule = allModules.find((m) => m.resource === resourceKey) ??
            allModules
                .reduce((acc, m) => {
                config_1.ModuleEntryPoints.forEach((entryPoint) => {
                    const entryPointModule = m[entryPoint];
                    if (entryPointModule) {
                        acc.push(entryPointModule);
                    }
                });
                return acc;
            }, [])
                .find(({ resource }) => resource === resourceKey);
        if (!linkedModule) {
            return 'default';
        }
        return linkedModule.render === 'native' ? 'nativeUI' : 'customUI';
    }
}
exports.ConfigFile = ConfigFile;
