import { SyncType } from '../models/Sync.js';
import localFileService from './file/local.service.js';
import { NangoError } from '../utils/error.js';
import { determineVersion, getInterval, isJsOrTsType } from '@nangohq/nango-yaml';
export const nangoConfigFile = 'nango.yaml';
export const SYNC_FILE_EXTENSION = 'js';
export function loadStandardConfig(configData, showMessages = false, isPublic) {
    try {
        if (!configData) {
            return { success: false, error: new NangoError('no_config_found'), response: null };
        }
        const version = determineVersion(configData);
        if (!configData.integrations) {
            return { success: true, error: null, response: [] };
        }
        const configServiceResponse = version === 'v1' ? convertConfigObject(configData) : convertV2ConfigObject(configData, showMessages, isPublic);
        return configServiceResponse;
    }
    catch (error) {
        return { success: false, error: new NangoError('error_loading_nango_config', error?.message), response: null };
    }
}
function getFieldsForModel(modelName, config) {
    const modelFields = [];
    if (isJsOrTsType(modelName)) {
        return null;
    }
    if (!config.models || Object.keys(config.models).length === 0) {
        return null;
    }
    // if it is an array of models, we still need to be able to recognize it
    const strippedModelName = modelName.replace(/\[\]/g, '');
    const modelData = config.models[strippedModelName];
    for (const fieldName in modelData) {
        const fieldType = modelData[fieldName];
        if (fieldName === '__extends') {
            const extendedModels = fieldType.split(',');
            for (const extendedModel of extendedModels) {
                const extendedFields = getFieldsForModel(extendedModel.trim(), config);
                if (extendedFields) {
                    modelFields.push(...extendedFields);
                }
            }
        }
        else if (typeof fieldType === 'object') {
            for (const subFieldName in fieldType) {
                const subFieldType = fieldType[subFieldName];
                modelFields.push({ name: `${fieldName}.${subFieldName}`, type: subFieldType });
            }
        }
        else {
            modelFields.push({ name: fieldName, type: fieldType?.trim() });
        }
    }
    return modelFields;
}
export function convertConfigObject(config) {
    const output = [];
    for (const providerConfigKey in config.integrations) {
        const syncs = [];
        const actions = [];
        const integration = config.integrations[providerConfigKey];
        let provider;
        if (integration['provider']) {
            provider = integration['provider'];
            // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
            delete integration['provider'];
        }
        for (const syncName in integration) {
            const sync = integration[syncName];
            const models = [];
            const syncReturns = Array.isArray(sync.returns) ? sync.returns : [sync.returns];
            if (sync.returns) {
                syncReturns.forEach((model) => {
                    const modelFields = getFieldsForModel(model, config);
                    if (modelFields) {
                        models.push({ name: model, fields: modelFields });
                    }
                });
            }
            const scopes = sync?.scopes || sync?.metadata?.scopes || [];
            const layout_mode = 'root';
            const flowObject = {
                name: syncName,
                runs: sync.runs || '',
                track_deletes: sync.track_deletes || false,
                type: sync.type || 'sync',
                auto_start: sync.auto_start === false ? false : true,
                attributes: sync.attributes || {},
                returns: syncReturns,
                models: models || [],
                description: sync?.description || sync?.metadata?.description || '',
                scopes: Array.isArray(scopes) ? scopes : String(scopes)?.split(','),
                endpoints: sync?.endpoints || [],
                nango_yaml_version: 'v1',
                layout_mode,
                json_schema: null
            };
            if (sync.type === 'action') {
                actions.push(flowObject);
            }
            else {
                syncs.push(flowObject);
            }
        }
        const simplifiedIntegration = {
            providerConfigKey,
            syncs,
            actions
        };
        if (provider) {
            simplifiedIntegration.provider = provider;
        }
        output.push(simplifiedIntegration);
    }
    return { success: true, error: null, response: output };
}
const assignEndpoints = (rawEndpoint, defaultMethod, singleAllowedMethod = false, showMessages = false) => {
    let endpoints = [];
    const endpoint = rawEndpoint.split(' ');
    if (endpoint.length > 1) {
        const method = singleAllowedMethod ? defaultMethod : endpoint[0]?.toUpperCase();
        if (singleAllowedMethod && showMessages && endpoint[0]?.toUpperCase() !== defaultMethod) {
            console.log(`A sync only allows for a ${defaultMethod} method. The provided ${endpoint[0]?.toUpperCase()} method will be ignored.`);
        }
        endpoints = [
            {
                [method]: endpoint[1]
            }
        ];
    }
    else {
        if (showMessages && !singleAllowedMethod) {
            console.log(`No HTTP method provided for endpoint ${endpoint[0]}. Defaulting to ${defaultMethod}.`);
        }
        endpoints = [
            {
                [defaultMethod]: endpoint[0]
            }
        ];
    }
    return endpoints;
};
const parseModelInEndpoint = (endpoint, allModelNames, inputModel, config) => {
    if (Object.keys(inputModel).length > 0) {
        return { success: false, error: new NangoError('conflicting_model_and_input'), response: null };
    }
    const modelNameWithIdentifier = endpoint.match(/{([^}]+)}/)?.[1];
    const modelNameWithIdentifierArray = modelNameWithIdentifier?.split(':');
    if (!modelNameWithIdentifierArray || modelNameWithIdentifierArray?.length < 2) {
        return { success: false, error: new NangoError('invalid_model_identifier', modelNameWithIdentifier), response: null };
    }
    const [modelName, identifier] = modelNameWithIdentifierArray;
    if (!allModelNames.includes(modelName)) {
        return { success: false, error: new NangoError('missing_model_name', modelName), response: null };
    }
    const modelFields = getFieldsForModel(modelName, config);
    if (modelFields) {
        const identifierModelFields = modelFields.filter((field) => field.name === identifier);
        if (identifierModelFields.length === 0) {
            return { success: false, error: new NangoError('missing_model_identifier', identifier), response: null };
        }
        inputModel.name = modelNameWithIdentifier;
        inputModel.fields = identifierModelFields;
    }
    return { success: true, error: null, response: inputModel };
};
const isEnabled = (script) => {
    if (script.enabled !== undefined) {
        return script.enabled;
    }
    return false;
};
export function convertV2ConfigObject(config, showMessages = false, isPublic) {
    const output = [];
    const allModelNames = config.models ? Object.keys(config.models) : [];
    for (const providerConfigKey in config.integrations) {
        const integration = config.integrations[providerConfigKey];
        let provider;
        if (integration['provider']) {
            provider = integration['provider'];
            // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
            delete integration['provider'];
        }
        // check that every endpoint is unique across syncs and actions
        const allEndpoints = [];
        const allModels = [];
        const syncs = integration['syncs'];
        const actions = integration['actions'];
        const postConnectionScripts = (integration['post-connection-scripts'] || []);
        const { success: builtSyncSuccess, error: builtSyncError, response: builtSyncs } = buildSyncs({ syncs, allModelNames, config, providerConfigKey, showMessages, isPublic, allModels, allEndpoints });
        if (!builtSyncSuccess || !builtSyncs) {
            return { success: builtSyncSuccess, error: builtSyncError, response: null };
        }
        const { success: builtActionSuccess, error: builtActionError, response: builtActions } = buildActions({ actions, allModelNames, config, providerConfigKey, showMessages, isPublic, allModels, allEndpoints });
        if (!builtActionSuccess || !builtActions) {
            return { success: builtActionSuccess, error: builtActionError, response: null };
        }
        const simplifiedIntegration = {
            providerConfigKey,
            syncs: builtSyncs ? builtSyncs : [],
            actions: builtActions ? builtActions : [],
            postConnectionScripts
        };
        output.push(simplifiedIntegration);
        if (provider) {
            simplifiedIntegration.provider = provider;
        }
    }
    return { success: true, error: null, response: output };
}
function formModelOutput({ integrationData, allModels, allModelNames, config, name, type }) {
    const models = [];
    if (integrationData.output) {
        const integrationDataReturns = Array.isArray(integrationData.output) ? integrationData.output : [integrationData.output];
        for (const model of integrationDataReturns) {
            if (allModels.includes(model) && type === 'sync') {
                const error = new NangoError('duplicate_model', { model, name, type: 'sync' });
                return { success: false, error, response: null };
            }
            if (!allModels.includes(model) && !isJsOrTsType(model)) {
                allModels.push(model);
            }
            const modelFields = getFieldsForModel(model, config);
            if (modelFields) {
                models.push({ name: model, fields: modelFields });
                const subModels = modelFields.filter((field) => {
                    if (typeof field?.type === 'string') {
                        const cleanType = field.type.replace(/\[\]/g, '');
                        return allModelNames.some((m) => m.includes(cleanType));
                    }
                    else {
                        return false;
                    }
                });
                for (const subModel of subModels) {
                    const subModelFields = getFieldsForModel(subModel.type, config);
                    if (subModelFields) {
                        const subModelName = subModel.type.replace(/\[\]/g, '');
                        models.push({ name: subModelName, fields: subModelFields });
                    }
                }
            }
        }
    }
    return { success: true, error: null, response: models };
}
function buildSyncs({ syncs, allModelNames, config, providerConfigKey, showMessages, isPublic, allModels, allEndpoints }) {
    const builtSyncs = [];
    for (const syncName in syncs) {
        const sync = syncs[syncName];
        const { success: modelSuccess, error: modelError, response: models } = formModelOutput({ integrationData: sync, allModels, allModelNames, config, name: syncName, type: 'sync' });
        if (!modelSuccess || !models) {
            return { success: false, error: modelError, response: null };
        }
        let inputModel = undefined;
        if (sync.input) {
            const modelFields = getFieldsForModel(sync.input, config);
            if (modelFields) {
                inputModel = {
                    name: sync.input,
                    fields: modelFields
                };
            }
        }
        let endpoints = [];
        if (sync?.endpoint) {
            if (Array.isArray(sync.endpoint)) {
                if (sync.endpoint?.length !== sync.output?.length) {
                    const error = new NangoError('endpoint_output_mismatch', syncName);
                    return { success: false, error, response: null };
                }
                for (const endpoint of sync.endpoint) {
                    endpoints.push(...assignEndpoints(endpoint, 'GET', true, showMessages));
                    if (!allEndpoints.includes(endpoint)) {
                        allEndpoints.push(endpoint);
                    }
                    else {
                        const error = new NangoError('duplicate_endpoint', endpoint);
                        return { success: false, error, response: null };
                    }
                }
            }
            else {
                endpoints = assignEndpoints(sync.endpoint, 'GET', true, showMessages);
                if (sync.output && Array.isArray(sync.output) && sync.output?.length > 1) {
                    const error = new NangoError('endpoint_output_mismatch', syncName);
                    return { success: false, error, response: null };
                }
                if (!allEndpoints.includes(sync.endpoint)) {
                    allEndpoints.push(sync.endpoint);
                }
                else {
                    const error = new NangoError('duplicate_endpoint', sync.endpoint);
                    return { success: false, error, response: null };
                }
            }
        }
        const scopes = sync?.scopes || sync?.metadata?.scopes || [];
        if (!sync?.runs && showMessages) {
            console.log(`No runs property found for sync "${syncName}". Defaulting to every day.`);
        }
        const runs = sync?.runs || 'every day';
        const interval = getInterval(runs, new Date());
        if (interval instanceof Error) {
            return { success: false, error: new NangoError(interval.message), response: null };
        }
        let webhookSubscriptions = [];
        if (sync['webhook-subscriptions']) {
            if (Array.isArray(sync['webhook-subscriptions'])) {
                webhookSubscriptions = sync['webhook-subscriptions'];
            }
            else {
                webhookSubscriptions = [sync['webhook-subscriptions']];
            }
        }
        const is_public = isPublic !== undefined ? isPublic : sync.is_public === true;
        const pre_built = isPublic !== undefined ? isPublic : sync.pre_built === true;
        const enabled = isEnabled(sync);
        const syncObject = {
            name: syncName,
            type: 'sync',
            models: models || [],
            sync_type: sync.sync_type?.toUpperCase() === SyncType.FULL ? SyncType.FULL : SyncType.INCREMENTAL,
            runs,
            track_deletes: sync.track_deletes || false,
            auto_start: sync.auto_start === false ? false : true,
            last_deployed: sync.updated_at || null,
            is_public,
            pre_built,
            version: sync.version || null,
            attributes: sync.attributes || {},
            input: inputModel,
            // a sync always returns an array
            returns: Array.isArray(sync.output) ? sync?.output : [sync.output],
            description: sync?.description || sync?.metadata?.description || '',
            scopes: Array.isArray(scopes) ? scopes : String(scopes)?.split(','),
            endpoints,
            nango_yaml_version: sync.nango_yaml_version || 'v2',
            webhookSubscriptions,
            enabled,
            layout_mode: localFileService.getLayoutMode(syncName, providerConfigKey, 'sync'),
            json_schema: null
        };
        if (sync.id) {
            syncObject.id = sync.id;
        }
        builtSyncs.push(syncObject);
    }
    return { success: true, error: null, response: builtSyncs };
}
function buildActions({ actions, allModelNames, config, providerConfigKey, showMessages, isPublic, allModels, allEndpoints }) {
    const builtActions = [];
    for (const actionName in actions) {
        const action = actions[actionName];
        const { success: modelSuccess, error: modelError, response: models } = formModelOutput({ integrationData: action, allModels, allModelNames, config, name: actionName, type: 'action' });
        if (!modelSuccess || !models) {
            return { success: false, error: modelError, response: null };
        }
        let inputModel = undefined;
        if (action.input) {
            if (action.input.includes('{') && action.input.includes('}')) {
                // find which model is in between the braces
                const modelName = action.input.match(/{([^}]+)}/)?.[1];
                if (!allModelNames.includes(modelName)) {
                    throw new Error(`Model ${modelName} not found included in models definition`);
                }
            }
            const modelFields = getFieldsForModel(action.input, config);
            if (modelFields) {
                inputModel = {
                    name: action.input,
                    fields: modelFields
                };
            }
        }
        let endpoints = [];
        let actionEndpoint;
        if (action?.endpoint) {
            if (Array.isArray(action?.endpoint)) {
                if (action?.endpoint?.length > 1) {
                    const error = new NangoError('action_single_endpoint', actionName);
                    return { success: false, error, response: null };
                }
                actionEndpoint = action?.endpoint[0];
            }
            else {
                actionEndpoint = action?.endpoint;
            }
            endpoints = assignEndpoints(actionEndpoint, 'POST', false, showMessages);
            if (actionEndpoint?.includes('{') && actionEndpoint.includes('}')) {
                const { success, error, response } = parseModelInEndpoint(actionEndpoint, allModelNames, inputModel, config);
                if (!success || !response) {
                    return { success, error, response: null };
                }
                inputModel = response;
            }
            if (!allEndpoints.includes(actionEndpoint)) {
                allEndpoints.push(actionEndpoint);
            }
            else {
                const error = new NangoError('duplicate_endpoint', actionEndpoint);
                return { success: false, error, response: null };
            }
        }
        const scopes = action?.scopes || action?.metadata?.scopes || [];
        const is_public = isPublic !== undefined ? isPublic : action.is_public === true;
        const pre_built = isPublic !== undefined ? isPublic : action.pre_built === true;
        const enabled = isEnabled(action);
        const actionObject = {
            name: actionName,
            type: 'action',
            models: models || [],
            runs: '',
            is_public,
            pre_built,
            version: action.version || null,
            last_deployed: action.updated_at || null,
            attributes: action.attributes || {},
            returns: Array.isArray(action.output) ? action.output : action.output ? [action.output] : [],
            description: action?.description || action?.metadata?.description || '',
            scopes: Array.isArray(scopes) ? scopes : String(scopes)?.split(','),
            input: inputModel,
            endpoints,
            nango_yaml_version: action.nango_yaml_version || 'v2',
            enabled,
            layout_mode: localFileService.getLayoutMode(actionName, providerConfigKey, 'action'),
            json_schema: null
        };
        if (action.id) {
            actionObject.id = action.id;
        }
        builtActions.push(actionObject);
    }
    return { success: true, error: null, response: builtActions };
}
//# sourceMappingURL=nango-config.service.js.map