import { error } from '../command-helpers.js';
export const AVAILABLE_CONTEXTS = ['all', 'production', 'deploy-preview', 'branch-deploy', 'dev'];
export const AVAILABLE_SCOPES = ['builds', 'functions', 'runtime', 'post_processing'];
/**
 * @param {string|undefined} context - The deploy context or branch of the environment variable value
 * @returns {Array<string|undefined>} The normalized context or branch name
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'context' implicitly has an 'any' type.
export const normalizeContext = (context) => {
    if (!context) {
        return context;
    }
    const CONTEXT_SYNONYMS = {
        dp: 'deploy-preview',
        prod: 'production',
    };
    context = context.toLowerCase();
    if (context in CONTEXT_SYNONYMS) {
        // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
        context = CONTEXT_SYNONYMS[context];
    }
    const forbiddenContexts = AVAILABLE_CONTEXTS.map((ctx) => `branch:${ctx}`);
    if (forbiddenContexts.includes(context)) {
        error(`The context ${context} includes a reserved keyword and is not allowed`);
    }
    context = context.replace(/^branch:/, '');
    return context;
};
/**
 * Finds a matching environment variable value from a given context
 * @param {Array<object>} values - An array of environment variable values from Envelope
 * @param {string} context - The deploy context or branch of the environment variable value
 * @returns {object<context: enum<dev,branch-deploy,deploy-preview,production,branch>, context_parameter: <string>, value: string>} The matching environment variable value object
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'values' implicitly has an 'any' type.
export const findValueInValues = (values, context) => 
// @ts-expect-error TS(7006) FIXME: Parameter 'val' implicitly has an 'any' type.
values.find((val) => {
    if (!AVAILABLE_CONTEXTS.includes(context)) {
        // the "context" option passed in is actually the name of a branch
        return val.context === 'all' || val.context_parameter === context;
    }
    return [context, 'all'].includes(val.context);
});
/**
 * Finds environment variables that match a given source
 * @param {object} env - The dictionary of environment variables
 * @param {enum<general,account,addons,ui,configFile>} source - The source of the environment variable
 * @returns {object} The dictionary of env vars that match the given source
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'env' implicitly has an 'any' type.
export const filterEnvBySource = (env, source) => 
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
Object.fromEntries(Object.entries(env).filter(([, variable]) => variable.sources[0] === source));
/**
 * Fetches data from Envelope
 * @param {string} accountId - The account id
 * @param {object} api - The api singleton object
 * @param {string} key - If present, fetch a single key (case-sensitive)
 * @param {string} siteId - The site id
 * @returns {Array<object>} An array of environment variables from the Envelope service
 */
// @ts-expect-error TS(7031) FIXME: Binding element 'accountId' implicitly has an 'any... Remove this comment to see the full error message
const fetchEnvelopeItems = async function ({ accountId, api, key, siteId }) {
    if (accountId === undefined) {
        return {};
    }
    try {
        // if a single key is passed, fetch that single env var
        if (key) {
            const envelopeItem = await api.getEnvVar({ accountId, key, siteId });
            return [envelopeItem];
        }
        // otherwise, fetch the entire list of env vars
        const envelopeItems = await api.getEnvVars({ accountId, siteId });
        return envelopeItems;
    }
    catch {
        // Collaborators aren't allowed to read shared env vars,
        // so return an empty array silently in that case
        return [];
    }
};
/**
 * Filters and sorts data from Envelope by a given context and/or scope
 * @param {string} context - The deploy context or branch of the environment variable value
 * @param {Array<object>} envelopeItems - An array of environment variables from the Envelope service
 * @param {enum<any,builds,functions,runtime,post_processing>} scope - The scope of the environment variables
 * @param {enum<general,account,addons,ui,configFile>} source - The source of the environment variable
 * @returns {object} A dicionary in the following format:
 * {
 *   FOO: {
 *     context: 'dev',
 *     scopes: ['builds', 'functions'],
 *     sources: ['ui'],
 *     value: 'bar',
 *   },
 *   BAZ: {
 *     context: 'branch',
 *     branch: 'staging',
 *     scopes: ['runtime'],
 *     sources: ['account'],
 *     value: 'bang',
 *   },
 * }
 */
// @ts-expect-error TS(7031) FIXME: Binding element 'source' implicitly has an 'any' t... Remove this comment to see the full error message
export const formatEnvelopeData = ({ context = 'dev', envelopeItems = [], scope = 'any', source }) => envelopeItems
    // filter by context
    .filter(({ values }) => Boolean(findValueInValues(values, context)))
    // filter by scope
    // @ts-expect-error TS(2339) FIXME: Property 'includes' does not exist on type 'never'... Remove this comment to see the full error message
    .filter(({ scopes }) => (scope === 'any' ? true : scopes.includes(scope)))
    // sort alphabetically, case insensitive
    // @ts-expect-error TS(2339) FIXME: Property 'key' does not exist on type 'never'.
    .sort((left, right) => (left.key.toLowerCase() < right.key.toLowerCase() ? -1 : 1))
    // format the data
    .reduce((acc, cur) => {
    // @ts-expect-error TS(2339) FIXME: Property 'values' does not exist on type 'never'.
    const { context: ctx, context_parameter: branch, value } = findValueInValues(cur.values, context);
    return {
        ...acc,
        // @ts-expect-error TS(2339) FIXME: Property 'key' does not exist on type 'never'.
        [cur.key]: {
            context: ctx,
            branch,
            // @ts-expect-error TS(2339) FIXME: Property 'scopes' does not exist on type 'never'.
            scopes: cur.scopes,
            sources: [source],
            value,
        },
    };
}, {});
/**
 * Collects env vars from multiple sources and arranges them in the correct order of precedence
 * @param {object} api - The api singleton object
 * @param {string} context - The deploy context or branch of the environment variable
 * @param {object} env - The dictionary of environment variables
 * @param {string} key - If present, fetch a single key (case-sensitive)
 * @param {boolean} raw - Return a dictionary of raw key/value pairs for only the account and site sources
 * @param {enum<any,builds,functions,runtime,post_processing>} scope - The scope of the environment variables
 * @param {object} siteInfo - The site object
 * @returns {object} An object of environment variables keys and their metadata
 */
// @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message
export const getEnvelopeEnv = async ({ api, context = 'dev', env, key = '', raw = false, scope = 'any', siteInfo }) => {
    const { account_slug: accountId, id: siteId } = siteInfo;
    const [accountEnvelopeItems, siteEnvelopeItems] = await Promise.all([
        // @ts-expect-error TS(2345) FIXME: Argument of type '{ api: any; accountId: any; key:... Remove this comment to see the full error message
        fetchEnvelopeItems({ api, accountId, key }),
        fetchEnvelopeItems({ api, accountId, key, siteId }),
    ]);
    const accountEnv = formatEnvelopeData({ context, envelopeItems: accountEnvelopeItems, scope, source: 'account' });
    const siteEnv = formatEnvelopeData({ context, envelopeItems: siteEnvelopeItems, scope, source: 'ui' });
    if (raw) {
        const entries = Object.entries({ ...accountEnv, ...siteEnv });
        return entries.reduce((obj, [envVarKey, metadata]) => ({
            ...obj,
            // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
            [envVarKey]: metadata.value,
        }), {});
    }
    const generalEnv = filterEnvBySource(env, 'general');
    const internalEnv = filterEnvBySource(env, 'internal');
    const addonsEnv = filterEnvBySource(env, 'addons');
    const configFileEnv = filterEnvBySource(env, 'configFile');
    // filter out configFile env vars if a non-configFile scope is passed
    const includeConfigEnvVars = /any|builds|post[-_]processing/.test(scope);
    // Sources of environment variables, in ascending order of precedence.
    return {
        ...generalEnv,
        ...accountEnv,
        ...(includeConfigEnvVars ? addonsEnv : {}),
        ...siteEnv,
        ...(includeConfigEnvVars ? configFileEnv : {}),
        ...internalEnv,
    };
};
/**
 * Returns a human-readable, comma-separated list of scopes
 * @param {Array<enum<builds,functions,runtime,post_processing>>} scopes - An array of scopes
 * @returns {string} A human-readable, comma-separated list of scopes
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'scopes' implicitly has an 'any' type.
export const getHumanReadableScopes = (scopes) => {
    const HUMAN_SCOPES = ['Builds', 'Functions', 'Runtime', 'Post processing'];
    const SCOPES_MAP = {
        builds: HUMAN_SCOPES[0],
        functions: HUMAN_SCOPES[1],
        runtime: HUMAN_SCOPES[2],
        post_processing: HUMAN_SCOPES[3],
        'post-processing': HUMAN_SCOPES[3],
    };
    if (!scopes) {
        // if `scopes` is not available, the env var comes from netlify.toml
        // env vars specified in netlify.toml are present in the `builds` and `post_processing` scope
        return 'Builds, Post processing';
    }
    if (scopes.length === Object.keys(HUMAN_SCOPES).length) {
        // shorthand instead of listing every available scope
        return 'All';
    }
    // @ts-expect-error TS(7006) FIXME: Parameter 'scope' implicitly has an 'any' type.
    return scopes.map((scope) => SCOPES_MAP[scope]).join(', ');
};
/**
 * Translates a Mongo env into an Envelope env
 * @param {object} env - The site's env as it exists in Mongo
 * @returns {Array<object>} The array of Envelope env vars
 */
export const translateFromMongoToEnvelope = (env = {}) => {
    const envVars = Object.entries(env).map(([key, value]) => ({
        key,
        scopes: AVAILABLE_SCOPES,
        values: [
            {
                context: 'all',
                value,
            },
        ],
    }));
    return envVars;
};
/**
 * Translates an Envelope env into a Mongo env
 * @param {Array<object>} envVars - The array of Envelope env vars
 * @param {string} context - The deploy context or branch of the environment variable
 * @returns {object} The env object as compatible with Mongo
 */
export const translateFromEnvelopeToMongo = (envVars = [], context = 'dev') => envVars
    // @ts-expect-error TS(2339) FIXME: Property 'key' does not exist on type 'never'.
    .sort((left, right) => (left.key.toLowerCase() < right.key.toLowerCase() ? -1 : 1))
    .reduce((acc, cur) => {
    // @ts-expect-error TS(2339) FIXME: Property 'values' does not exist on type 'never'.
    const envVar = cur.values.find((val) => [context, 'all'].includes(val.context_parameter || val.context));
    if (envVar && envVar.value) {
        return {
            ...acc,
            // @ts-expect-error TS(2339) FIXME: Property 'key' does not exist on type 'never'.
            [cur.key]: envVar.value,
        };
    }
    return acc;
}, {});