"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateEndpoint = exports.validateMetadata = exports.buildConnectors = void 0;
const r = __importStar(require("@ubio/request"));
const ajv_1 = __importDefault(require("ajv"));
const _1 = require(".");
const index_js_1 = require("./model/index.js");
const credentials_js_1 = require("./services/credentials.js");
const ajv = new ajv_1.default({
    messages: true,
});
function buildConnectors(namespace, spec, _options = {}) {
    const metadata = {
        icon: spec.icon,
        auth: spec.auth,
        baseUrl: spec.baseUrl,
        docUrl: spec.docUrl,
    };
    validateMetadata(metadata, true);
    const actions = {};
    for (const endpoint of spec.endpoints) {
        const { valid } = validateEndpoint(endpoint);
        if (!valid) {
            continue;
        }
        const ConnectorActionClass = buildConnectorClass({ namespace, metadata, endpoint });
        actions[ConnectorActionClass.$type] = ConnectorActionClass;
    }
    return actions;
}
exports.buildConnectors = buildConnectors;
function buildConnectorClass(spec) {
    const { namespace, metadata, endpoint } = spec;
    const type = `${namespace}.${endpoint.name}.${endpoint.method.toLocaleLowerCase()}`;
    class ConnectorAction extends _1.Action {
        constructor() {
            super(...arguments);
            this.$baseUrl = metadata.baseUrl;
            this.$endpoint = endpoint;
            this.$response = undefined;
            this.$result = undefined;
        }
        init(spec) {
            super.init(spec);
            this.auth = spec.auth ?? null;
            if (this.pipeline.length === 0) {
                this.pipeline = new index_js_1.Pipeline(this, 'pipeline', getPipeline(this.$endpoint));
            }
        }
        get $credentials() { return this.$engine.get(credentials_js_1.CredentialsService); }
        reset() {
            super.reset();
            this.$response = undefined;
            this.$result = undefined;
        }
        async exec() {
            const data = await this.retry(async () => {
                const el = await this.selectOne(this.pipeline);
                return el.value;
            });
            _1.util.checkType(data, 'object', 'Parameters');
            const { options, path } = this.getRequestSpec(data);
            const { method } = this.$endpoint;
            const auth = await this.$credentials.getAuthAgent(this.auth);
            const request = new r.Request({
                baseUrl: this.$baseUrl,
                auth,
            });
            const res = await request.sendRaw(method, path, options);
            this.$response = await parseResponse(res);
            this.$result = this.$response.body;
        }
        getRequestSpec(evaluatedParams) {
            let isFormData = false;
            let path = this.$endpoint.path;
            const options = {
                headers: {
                    'content-type': 'application/json',
                    ...(evaluatedParams.headers ?? {}),
                },
                body: evaluatedParams.body ?? null
            };
            for (const param of this.$endpoint.parameters) {
                const { key } = param;
                let val = evaluatedParams[key] ?? param.default;
                if (!val && param.location === 'header') {
                    val = options.headers[key] ?? undefined;
                }
                if (!val && param.location === 'body') {
                    val = options.body && options.body[key] ? options.body[key] : undefined;
                }
                if (val === undefined) {
                    if (param.required) {
                        throw _1.util.createError({
                            code: 'ParameterValidationError',
                            message: `Parameter \`${key}\` is required`,
                            details: {
                                parameters: evaluatedParams
                            },
                            retry: false,
                        });
                    }
                    continue;
                }
                switch (param.location) {
                    case 'header':
                        options.headers = { ...options.headers, [key]: val };
                        break;
                    case 'query':
                        options.query = { ...options.query, [key]: val };
                        break;
                    case 'path':
                        path = path.replace(`{${key}}`, val);
                        break;
                    case 'body':
                        options.body = { ...options.body, [key]: val };
                        break;
                    case 'formData':
                        isFormData = true;
                        options.body = { ...options.body, [key]: val };
                        break;
                }
            }
            if (isFormData) {
                options.headers['content-type'] = 'application/x-www-form-urlencoded';
                const formData = new URLSearchParams(options.body ?? {});
                options.body = formData.toString();
            }
            else {
                options.body = options.body != null ? JSON.stringify(options.body) : null;
            }
            return { options, path };
        }
    }
    ConnectorAction.$type = type;
    ConnectorAction.$help = endpoint.description + (metadata.docUrl ? `\n\n Check documentation here: ${metadata.docUrl}` : '');
    ConnectorAction.$icon = `${!metadata.icon.match(/http/) ? 'fab ' : ''}${metadata.icon}`;
    __decorate([
        _1.params.Credentials({
            label: 'Auth',
            providerName: namespace,
            configs: metadata.auth,
        }),
        __metadata("design:type", Object)
    ], ConnectorAction.prototype, "auth", void 0);
    __decorate([
        _1.params.Pipeline({
            label: 'Parameters',
        }),
        __metadata("design:type", index_js_1.Pipeline)
    ], ConnectorAction.prototype, "pipeline", void 0);
    __decorate([
        _1.params.Outcome({
            label: 'Response',
            placeholder: 'Run the action to see the outcome value.',
        }),
        __metadata("design:type", Object)
    ], ConnectorAction.prototype, "$response", void 0);
    __decorate([
        _1.params.Outcome({
            label: 'Result',
            placeholder: 'Run the action to see the outcome value.',
        }),
        __metadata("design:type", Object)
    ], ConnectorAction.prototype, "$result", void 0);
    return ConnectorAction;
}
async function parseResponse(res) {
    const response = {
        url: res.url,
        status: res.status,
        statusText: res.statusText,
        headers: {},
        body: null,
    };
    for (const [k, v] of res.headers) {
        response.headers[k.toLowerCase()] = v;
    }
    if (response.headers['content-type'].includes('application/json')) {
        response.body = await res.json();
    }
    else {
        response.body = await res.text();
    }
    return response;
}
const endpointSchema = {
    type: 'object',
    required: [
        'name', 'description', 'path',
        'method', 'parameters'
    ],
    properties: {
        name: { type: 'string' },
        description: { type: 'string' },
        path: { type: 'string' },
        method: { type: 'string' },
        parameters: {
            type: 'array',
            items: {
                type: 'object',
                required: ['key', 'location'],
                properties: {
                    key: { type: 'string' },
                    location: {
                        type: 'string',
                        enum: ['path', 'query', 'body', 'formData', 'header']
                    },
                    description: { type: 'string' },
                    required: { type: 'boolean', default: false },
                    default: {}
                },
            }
        },
    },
};
const metadataSchema = {
    type: 'object',
    required: ['auth', 'baseUrl', 'icon'],
    properties: {
        icon: { type: 'string' },
        baseUrl: { type: 'string' },
        auth: {
            type: 'array',
            items: {
                type: 'object',
                required: ['type'],
                properties: {
                    type: { type: 'string', enum: ['basic', 'bearer', 'oauth1', 'oauth2'] },
                }
            }
        },
        docUrl: { type: 'string' }
    }
};
function validate(schema, value, throwInvalid = false) {
    const validator = ajv.compile(schema);
    const valid = validator(value);
    if (!valid && throwInvalid) {
        throw _1.util.createError({
            code: 'ValidationError',
            message: 'Spec does not conform to schema',
            details: {
                messages: validator.errors?.map(_ => _.schemaPath + ': ' + _.message),
            },
            retry: false,
        });
    }
    return {
        valid,
        details: validator.errors,
    };
}
function validateMetadata(value, throwInvalid = false) {
    return validate(metadataSchema, value, throwInvalid);
}
exports.validateMetadata = validateMetadata;
function validateEndpoint(value, throwInvalid = false) {
    return validate(endpointSchema, value, throwInvalid);
}
exports.validateEndpoint = validateEndpoint;
function getPipeline(endpoint) {
    const pipeline = [
        {
            type: 'Value.getJson',
            value: getParametersJsonString(endpoint.parameters),
        }
    ];
    if (endpoint.body) {
        const body = [
            '// Body for this endpoint.',
            '// Please take a look at the documentation for the detailed schema',
        ];
        body.push(JSON.stringify(endpoint.body, null, 2));
        pipeline.push({
            type: 'Object.setPath',
            path: '/body',
            pipeline: [{
                    type: 'Value.getJson',
                    value: body.join('\n'),
                }]
        });
    }
    return pipeline;
}
function getParametersJsonString(params) {
    const str = [
        '// Parameters for this endpoint are shown below. ',
        '// Use Object.setPath or Object.compose to set values. ',
        '{',
    ];
    params.forEach(param => {
        const defaultValue = typeof param.default === 'string' ? `"${param.default}"` : param.default;
        str.push(`  "${param.key}": ${defaultValue ?? null}, // ${param.required ? '*' : ''}${param.description}`);
    });
    str.push('}');
    return str.join('\n');
}
//# sourceMappingURL=connector.js.map