import * as Sampler from 'openapi-sampler';
import merge from 'deepmerge';
import { isObject } from '@redocly/theme/core/openapi';
import { areArraysEqual, serializeParameterValue, getSecurityDetails, getParameterValue, queryString, isArrayOfObjects, deleteEmptyArrayItem, } from '../../utils/index.js';
import { normalizeMimeType, arrayMergeStrategy } from '../utils.js';
import { MediaTypes } from '../../constants.js';
import { HTTPSnippet } from './httpsnippet/index.js';
import { getActiveMediaType } from '../../models/mediaContent.js';
import StringUtility from './httpsnippet/helpers/string-utility.js';
export function safeSample(schema, options, document) {
    try {
        return Sampler.sample(schema, options, document);
    }
    catch (e) {
        console.error(`Error sampling schema: ${e.message}`);
        return null;
    }
}
function getSelectedSecurityScheme(operationSecurity, activeSecuritySchemeIds) {
    return operationSecurity.find((sec) => {
        const secSchemesIds = sec.schemes.map((scheme) => scheme.id);
        return areArraysEqual(secSchemesIds, activeSecuritySchemeIds || []);
    });
}
function mapOperationToHAR(operation, { exampleName = '', skipOptionalParameters = false, withOAuth2Call = false, spec, generatedPayloadSamplesMaxDepth = 8, pathParams = {}, properties = {}, activeMimeName = '', }, { environment, requestValues, activeSecuritySchemeIds, }) {
    const envServer = environment.server;
    const activeServer = operation.servers.find((server) => server.url === envServer) || operation.servers[0];
    const selectedSecurityScheme = getSelectedSecurityScheme(operation.security, activeSecuritySchemeIds);
    const { securityHeaders, securityCookies, securityQueries, securityOAuth2ExtraCalls, basicAuth } = getSecurityData(selectedSecurityScheme || operation.security?.[0], withOAuth2Call, environment); // TODO: add dropdown for security type select
    const activeMimeType = operation?.requestBody?.content &&
        getActiveMediaType(operation.requestBody.content, activeMimeName);
    const samplerOptions = {
        skipNonRequired: skipOptionalParameters,
        skipReadOnly: true,
        maxSampleDepth: generatedPayloadSamplesMaxDepth,
        quiet: true,
    };
    const tryItRequestValues = requestValues;
    let hasContentTypeHeader = false;
    const headers = operation.parameters
        .filter((param) => param.in === 'header')
        .filter((param) => !(skipOptionalParameters && !param.required))
        .map((header) => ({
        name: header.name,
        value: tryItRequestValues['header']?.[header.name] ||
            getParameterValue('header', header.name) ||
            header.serverValues?.[envServer]?.example ||
            header.example ||
            serializeParameterValue(header, safeSample(header.schema.rawSchema, samplerOptions, spec)),
    }))
        .map((header) => {
        if (
        // Overwrite content-type header from selected value in dropdown
        activeMimeType?.name &&
            header.name.toLowerCase() === 'content-type') {
            hasContentTypeHeader = true;
            header.value = activeMimeType.name;
        }
        return header;
    })
        .concat(securityHeaders);
    if (!hasContentTypeHeader && activeMimeType?.name) {
        headers.unshift({
            name: 'Content-Type',
            value: activeMimeType?.name,
        });
    }
    const cookies = operation.parameters
        .filter((param) => param.in === 'cookie')
        .filter((param) => !(skipOptionalParameters && !param.required))
        .map((cookie) => queryString.parse(serializeParameterValue(cookie, cookie.serverValues?.[envServer]?.example ||
        cookie.example ||
        safeSample(cookie.schema.rawSchema, samplerOptions, spec))))
        .reduce((acc, current) => {
        for (const [key, value] of Object.entries(current)) {
            acc.push({
                name: key,
                value: String(tryItRequestValues['cookie']?.[key] || value),
            });
        }
        return acc;
    }, [])
        .concat(securityCookies);
    const queryParams = operation.parameters
        .filter((param) => param.in === 'query')
        .filter((param) => !(skipOptionalParameters && !param.required))
        .map((query) => {
        return queryString.parse(serializeParameterValue(query, query.serverValues?.[envServer]?.example ||
            query.example ||
            safeSample(query.schema.rawSchema, samplerOptions, spec)));
    })
        .reduce((acc, current) => {
        for (const [key, value] of Object.entries(current)) {
            acc.push({
                name: key,
                value: (isArrayOfObjects(tryItRequestValues['query']?.[key]) &&
                    JSON.stringify(deleteEmptyArrayItem(tryItRequestValues['query']?.[key]))) ||
                    String(tryItRequestValues['query']?.[key] || value),
            });
        }
        return acc;
    }, [])
        .concat(securityQueries);
    const server = activeServer?.url.replace(/\/$/, ''); // remove trailing slash
    const path = operation.path.replace(/^\//, '');
    const url = `${server}/${path}`;
    // should we use examples instead of name?
    const pathParameters = operation.parameters
        .filter((param) => param.in === 'path')
        .reduce((acc, value) => {
        const { in: _in, name } = value;
        acc[name] =
            tryItRequestValues['path']?.[name] ||
                value.serverValues?.[envServer]?.example ||
                value.example ||
                pathParams[name] ||
                getParameterValue(_in, name);
        return acc;
    }, {});
    const contentType = activeMimeType?.name.toLowerCase();
    const useFormValues = contentType === MediaTypes.MULTIPART || contentType === MediaTypes.URL_ENCODED;
    const activeExample = useFormValues
        ? activeMimeType?.formExamples?.[exampleName]
        : activeMimeType?.examples?.[exampleName];
    // Getting example by name or just first example in name is not provided
    const examples = useFormValues
        ? activeMimeType?.formExamples || {}
        : activeMimeType?.examples || {};
    const [firstExample] = Object.values(examples);
    const serverSpecificValue = activeExample?.serverValues?.[envServer]?.value ||
        firstExample?.serverValues?.[envServer]?.value;
    const processXmlSchema = (values) => {
        if (!values || typeof values !== 'object' || !activeMimeType?.schema?.rawSchema) {
            return null;
        }
        return safeSample(populateXmlSchemaFields(activeMimeType.schema.rawSchema, values), { ...samplerOptions, format: 'xml' }, spec);
    };
    const getExampleValue = () => {
        if (tryItRequestValues?.['body']) {
            return tryItRequestValues['body'];
        }
        if (contentType === MediaTypes.XML) {
            const xmlProcessed = processXmlSchema(serverSpecificValue) ||
                processXmlSchema(activeExample?.value) ||
                processXmlSchema(firstExample?.value);
            if (xmlProcessed) {
                return xmlProcessed;
            }
            if (activeMimeType?.schema) {
                return safeSample(activeMimeType.schema.rawSchema, { ...samplerOptions, format: 'xml' }, spec);
            }
        }
        else {
            const sampledValue = activeMimeType?.schema &&
                safeSample(activeMimeType.schema.rawSchema, { ...samplerOptions, format: 'json' }, spec);
            return serverSpecificValue || activeExample?.value || firstExample?.value || sampledValue;
        }
        return '';
    };
    const exampleValue = getExampleValue();
    let textPostData = '';
    let postDataParams = [];
    switch (normalizeMimeType(contentType)) {
        case MediaTypes.JSON:
            textPostData = JSON.stringify(getDataRequest(exampleValue, properties));
            break;
        case MediaTypes.XML:
            textPostData = exampleValue;
            break;
        case MediaTypes.URL_ENCODED:
        case MediaTypes.MULTIPART:
            textPostData = queryString.stringify(getDataRequest(exampleValue, properties));
            postDataParams = objectToHarParams(getDataRequest(exampleValue, properties));
            break;
        default:
            textPostData = String(exampleValue || '');
    }
    const postData = textPostData
        ? {
            mimeType: activeMimeType?.name || MediaTypes.OCTET_STREAM,
            text: textPostData,
            params: postDataParams,
        }
        : undefined;
    const allResponseCodes = operation.responses.map((response) => response.code);
    return {
        method: operation.httpVerb,
        url,
        httpVersion: 'HTTP/1.1',
        cookies: cookies,
        headers: headers,
        queryString: queryParams,
        postData,
        headersSize: -1,
        bodySize: -1,
        securityOAuth2ExtraCalls,
        basicAuth,
        pathParameters,
        serverVariables: environment,
        allResponseCodes,
    };
}
const defaultOptions = { withImports: true, withComments: false };
// Map language to 'httpsnippet' library target code
const langToSnippetConfig = {
    curl: {
        code: 'shell',
        defaultTarget: 'curl',
        defaultOptions: { ...defaultOptions, short: true },
    },
    JavaScript: {
        code: 'javascript',
        defaultTarget: 'fetch',
        defaultOptions: {
            ...defaultOptions,
            withImports: false,
        },
    },
    'Node.js': {
        code: 'node',
        defaultTarget: 'fetch',
        defaultOptions: { ...defaultOptions },
    },
    Python: {
        code: 'python',
        defaultTarget: 'requests',
        defaultOptions: { ...defaultOptions },
    },
    'Java8+Apache': {
        code: 'java8',
        defaultTarget: 'apachehttp',
        defaultOptions: { ...defaultOptions },
    },
    Java: {
        code: 'java',
        defaultTarget: 'httpclient',
        defaultOptions: { ...defaultOptions },
    },
    'C#': {
        code: 'csharp',
        defaultTarget: 'httpclient',
        defaultOptions: { ...defaultOptions },
    },
    'C#+Newtonsoft': {
        code: 'csharpNewtonsoft',
        defaultTarget: 'httpclient',
        defaultOptions: { ...defaultOptions },
    },
    Go: {
        code: 'go',
        defaultTarget: 'http.DefaultClient',
        defaultOptions: { ...defaultOptions },
    },
    PHP: {
        code: 'php',
        defaultTarget: 'curl',
        defaultOptions: { ...defaultOptions },
    },
    Ruby: {
        code: 'ruby',
        defaultTarget: 'net::http',
        defaultOptions: { ...defaultOptions },
    },
    R: {
        code: 'r',
        defaultTarget: 'httr',
        defaultOptions: { ...defaultOptions },
    },
};
export function getCodeSample({ lang, operation, exampleName, pathParams, properties, options = {}, environment, translate, }) {
    try {
        const { skipOptionalParameters, withOAuth2Call, spec, generatedPayloadSamplesMaxDepth, store: operationStore, activeMimeName, } = options;
        const har = mapOperationToHAR(operation, {
            exampleName,
            pathParams,
            properties,
            skipOptionalParameters,
            withOAuth2Call,
            spec,
            generatedPayloadSamplesMaxDepth,
            activeMimeName,
        }, {
            environment,
            requestValues: operationStore.requestValues,
            activeSecuritySchemeIds: operationStore.activeSecuritySchemeIds,
        });
        const snippet = new HTTPSnippet(har);
        if (!langToSnippetConfig[lang]) {
            return translate('openapi.unsupportedLanguage', 'Language is not supported.');
        }
        return snippet.convert(langToSnippetConfig[lang].code, langToSnippetConfig[lang].defaultTarget, {
            ...langToSnippetConfig[lang].defaultOptions,
            ...options,
        });
    }
    catch (e) {
        console.error(e);
        return translate('openapi.failedToGenerateCodeSample', 'Failed to generate code sample.');
    }
}
function getSecurityData(requirement, withOAuth2Call, environment) {
    const securityData = {
        securityHeaders: [],
        securityCookies: [],
        securityQueries: [],
        securityOAuth2ExtraCalls: [],
        basicAuth: undefined,
    };
    const serverUrl = environment?.server;
    for (const scheme of requirement?.schemes || []) {
        const authDetails = getSecurityDetails(scheme.id, environment);
        const serverCredentials = serverUrl && scheme.serverValues && scheme.serverValues[serverUrl];
        const operationSecurityValue = {
            access_token: serverCredentials?.['x-defaultAccessToken'] || scheme['x-defaultAccessToken'],
            token_type: serverCredentials?.['x-defaultTokenType'] || scheme['x-defaultTokenType'],
            client_id: serverCredentials?.['x-defaultClientId'] || scheme['x-defaultClientId'],
            client_secret: serverCredentials?.['x-defaultClientSecret'] || scheme['x-defaultClientSecret'],
        };
        const operationUsername = serverCredentials?.['x-defaultUsername'] || scheme['x-defaultUsername'];
        const operationPassword = serverCredentials?.['x-defaultPassword'] || scheme['x-defaultPassword'];
        const operationScopes = serverCredentials && 'scopes' in serverCredentials
            ? serverCredentials.scopes || []
            : scheme.scopes || [];
        switch (scheme.type) {
            case 'openIdConnect':
                securityData.securityHeaders?.push({
                    name: 'Authorization',
                    value: operationSecurityValue.access_token
                        ? `${operationSecurityValue.token_type || 'Bearer'} ${operationSecurityValue.access_token}`
                        : authDetails.token
                            ? `${operationSecurityValue.token_type || authDetails.token.token_type || 'Bearer'} ${authDetails.token.access_token}`
                            : 'Bearer <YOUR_TOKEN_HERE>',
                });
                break;
            case 'oauth2': {
                const authTokenValue = operationSecurityValue.access_token
                    ? `${operationSecurityValue.token_type || 'Bearer'} ${operationSecurityValue.access_token}`
                    : authDetails.token
                        ? `${authDetails.token.token_type || 'Bearer'} ${authDetails.token.access_token}`
                        : 'Bearer <YOUR_TOKEN_HERE>';
                if (scheme.flows.clientCredentials && withOAuth2Call) {
                    securityData.securityOAuth2ExtraCalls.push(getOAuth2ClientCredsCallHar(scheme.flows.clientCredentials, operationScopes, {
                        ...authDetails,
                        client_id: operationSecurityValue.client_id || authDetails.client_id,
                        client_secret: operationSecurityValue.client_secret || authDetails.client_secret,
                    }));
                }
                else if (scheme.flows.password && withOAuth2Call) {
                    securityData.securityOAuth2ExtraCalls.push(getOAuth2PasswordCallHar(scheme.flows.password, operationScopes, {
                        ...authDetails,
                        client_id: operationSecurityValue.client_id || authDetails.client_id,
                        client_secret: operationSecurityValue.client_secret || authDetails.client_secret,
                    }));
                }
                securityData.securityHeaders?.push({ name: 'Authorization', value: authTokenValue });
                break;
            }
            case 'apiKey': {
                const value = operationSecurityValue.access_token || authDetails.raw || 'YOUR_API_KEY_HERE';
                if (scheme.in === 'header') {
                    securityData.securityHeaders?.push({
                        name: scheme.name,
                        value,
                    });
                }
                if (scheme.in === 'cookie') {
                    securityData.securityCookies.push({
                        name: scheme.name,
                        value,
                    });
                }
                if (scheme.in === 'query') {
                    securityData.securityQueries.push({
                        name: scheme.name,
                        value,
                    });
                }
                break;
            }
            case 'http':
                if (scheme.scheme === 'basic') {
                    securityData.basicAuth = {
                        username: operationUsername ||
                            authDetails.username ||
                            `<${StringUtility.toSnakeCase('Username')}>`,
                        password: operationPassword ||
                            authDetails.password ||
                            `<${StringUtility.toSnakeCase('Password')}>`,
                    };
                }
                else {
                    const token = operationSecurityValue?.access_token ||
                        authDetails.token?.access_token ||
                        `<YOUR_${scheme.bearerFormat || 'TOKEN'}_HERE>`;
                    securityData.securityHeaders?.push({
                        name: 'Authorization',
                        value: `${capitalizeFirst(scheme.scheme || 'bearer')} ${token}`,
                    });
                }
                break;
        }
    }
    return securityData;
}
function getOAuth2PasswordCallHar(flow, scopes, authDetails) {
    return {
        method: 'POST',
        url: flow.tokenUrl,
        httpVersion: 'HTTP/1.1',
        headers: [
            { name: 'Content-Type', value: MediaTypes.URL_ENCODED },
            { name: 'Accept', value: MediaTypes.JSON },
        ],
        queryString: [],
        postData: {
            mimeType: MediaTypes.URL_ENCODED,
            text: '',
            params: [
                { name: 'grant_type', value: 'password' },
                { name: 'client_id', value: authDetails.client_id || 'YOUR_CLIENT_ID' },
                { name: 'client_secret', value: authDetails.client_secret || 'YOUR_CLIENT_SECRET' },
                { name: 'username', value: authDetails.username || '<username>' },
                { name: 'password', value: authDetails.password || '<password>' },
                scopes.length ? { name: 'scope', value: scopes.join(' ') } : undefined,
            ].filter(isDefined),
        },
        cookies: [],
        headersSize: -1,
        bodySize: -1,
        securityOAuth2ExtraCalls: [],
    };
}
function getOAuth2ClientCredsCallHar(flow, scopes, authDetails) {
    return {
        method: 'POST',
        url: flow.tokenUrl,
        httpVersion: 'HTTP/1.1',
        headers: [
            { name: 'Content-Type', value: MediaTypes.URL_ENCODED },
            { name: 'Accept', value: MediaTypes.JSON },
        ],
        queryString: [],
        postData: {
            mimeType: MediaTypes.URL_ENCODED,
            text: '',
            params: [
                { name: 'grant_type', value: 'client_credentials' },
                { name: 'client_id', value: authDetails.client_id || 'YOUR_CLIENT_ID' },
                { name: 'client_secret', value: authDetails.client_secret || 'YOUR_CLIENT_SECRET' },
                scopes.length ? { name: 'scope', value: scopes.join(' ') } : undefined,
            ].filter(isDefined),
        },
        cookies: [],
        headersSize: -1,
        bodySize: -1,
        securityOAuth2ExtraCalls: [],
    };
}
export function getDataRequest(exampleValue, properties) {
    if (exampleValue) {
        if (isObject(exampleValue)) {
            if (Object.keys(exampleValue).length > 0 && Object.keys(properties).length === 0) {
                return exampleValue;
            }
            else {
                return merge(exampleValue, properties, { arrayMerge: arrayMergeStrategy });
            }
        }
        try {
            return merge(JSON.parse(exampleValue), properties, { arrayMerge: arrayMergeStrategy });
        }
        catch {
            return false;
        }
    }
    return false;
}
function objectToHarParams(obj, parentKey = '') {
    const res = [];
    for (const [key, value] of Object.entries(obj)) {
        const currentKey = parentKey ? `${parentKey}[${key}]` : key;
        if (typeof value === 'object' && value !== null) {
            res.push(...objectToHarParams(value, currentKey));
        }
        else {
            const name = Array.isArray(obj) && typeof value === 'string' && parentKey ? parentKey : currentKey;
            res.push({ name, value: String(value) });
        }
    }
    return res;
}
export function isDefined(arg) {
    return arg !== undefined;
}
export function capitalizeFirst(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
}
function populateXmlSchemaFields(schema, values) {
    if (!schema || typeof schema !== 'object' || !values || typeof values !== 'object') {
        return schema;
    }
    const updatedSchema = JSON.parse(JSON.stringify(schema));
    function populateValues(schemaNode, valueNode) {
        if (!schemaNode ||
            !valueNode ||
            typeof schemaNode !== 'object' ||
            typeof valueNode !== 'object') {
            return;
        }
        if (schemaNode.properties) {
            for (const propName in schemaNode.properties) {
                if (valueNode[propName] !== undefined) {
                    const propSchema = schemaNode.properties[propName];
                    const propValue = valueNode[propName];
                    if (propSchema.type === 'object' &&
                        typeof propValue === 'object' &&
                        !Array.isArray(propValue)) {
                        populateValues(propSchema, propValue);
                    }
                    else if (propSchema.type === 'array' &&
                        Array.isArray(propValue) &&
                        propValue.length > 0) {
                        // For arrays, use the first item from values array to update item examples
                        const firstItem = propValue[0];
                        if (propSchema.items && propSchema.items.properties) {
                            populateValues(propSchema.items, firstItem);
                        }
                    }
                    else {
                        propSchema.example = propValue;
                    }
                }
                // If value doesn't exist for this property, just skip it (keep original example)
            }
        }
    }
    populateValues(updatedSchema, values);
    return updatedSchema;
}
//# sourceMappingURL=generator.js.map