"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 __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getClientVersionHeader = exports.httpClient = void 0;
const abort_controller_x_1 = require("abort-controller-x");
const openidConfigurationGetter_js_1 = __importDefault(require("../misc/openidConfigurationGetter.js"));
const errors_js_1 = require("../errors.js");
const version_js_1 = require("../version.js");
const auth_js_1 = require("./auth.js");
class ConnectionREST {
    apiKey;
    headers;
    authEnabled;
    host;
    http;
    oidcAuth;
    constructor(params) {
        params = this.sanitizeParams(params);
        this.host = params.host;
        this.headers = params.headers;
        this.http = (0, exports.httpClient)(params);
        this.authEnabled = this.parseAuthParams(params);
    }
    parseAuthParams(params) {
        if (params.authClientSecret && params.apiKey) {
            throw new errors_js_1.WeaviateInvalidInputError('must provide one of authClientSecret (OIDC) or apiKey, cannot provide both');
        }
        if (params.authClientSecret) {
            this.oidcAuth = new auth_js_1.OidcAuthenticator(this.http, params.authClientSecret);
            return true;
        }
        if (params.apiKey) {
            this.apiKey = params.apiKey?.apiKey;
            return true;
        }
        return false;
    }
    sanitizeParams(params) {
        // Remove trailing slashes from the host
        while (params.host.endsWith('/')) {
            params.host = params.host.slice(0, -1);
        }
        const protocolPattern = /^(https?|ftp|file)(?::\/\/)/;
        const extractedSchemeMatch = params.host.match(protocolPattern);
        // Check for the existence of scheme in params
        if (params.scheme) {
            // If the host contains a scheme different than provided scheme, replace it and throw a warning
            if (extractedSchemeMatch && extractedSchemeMatch[1] !== `${params.scheme}`) {
                throw new errors_js_1.WeaviateInvalidInputError(`The host contains a different protocol than specified in the scheme (scheme: ${params.scheme} != host: ${extractedSchemeMatch[1]})`);
            }
            else if (!extractedSchemeMatch) {
                // If no scheme in the host, simply prefix with the provided scheme
                params.host = `${params.scheme}://${params.host}`;
            }
            // If there's no scheme in params, ensure the host starts with a recognized protocol
        }
        else if (!extractedSchemeMatch) {
            throw new errors_js_1.WeaviateInvalidInputError('The host must start with a recognized protocol (e.g., http or https) if no scheme is provided.');
        }
        return params;
    }
    postReturn = (path, payload) => {
        if (this.authEnabled) {
            return this.login().then((token) => this.http.post(path, payload, true, token));
        }
        return this.http.post(path, payload, true, '');
    };
    postEmpty = (path, payload) => {
        if (this.authEnabled) {
            return this.login().then((token) => this.http.post(path, payload, false, token));
        }
        return this.http.post(path, payload, false, '');
    };
    put = (path, payload, expectReturnContent = true) => {
        if (this.authEnabled) {
            return this.login().then((token) => this.http.put(path, payload, expectReturnContent, token));
        }
        return this.http.put(path, payload, expectReturnContent);
    };
    patch = (path, payload) => {
        if (this.authEnabled) {
            return this.login().then((token) => this.http.patch(path, payload, token));
        }
        return this.http.patch(path, payload);
    };
    delete = (path, payload, expectReturnContent = false) => {
        if (this.authEnabled) {
            return this.login().then((token) => this.http.delete(path, payload, expectReturnContent, token));
        }
        return this.http.delete(path, payload, expectReturnContent);
    };
    head = (path, payload) => {
        if (this.authEnabled) {
            return this.login().then((token) => this.http.head(path, payload, token));
        }
        return this.http.head(path, payload);
    };
    get = (path, expectReturnContent = true) => {
        if (this.authEnabled) {
            return this.login().then((token) => this.http.get(path, expectReturnContent, token));
        }
        return this.http.get(path, expectReturnContent);
    };
    login = async () => {
        if (this.apiKey) {
            return this.apiKey;
        }
        if (!this.oidcAuth) {
            return '';
        }
        const localConfig = await new openidConfigurationGetter_js_1.default(this.http).do();
        if (localConfig === undefined) {
            console.warn('client is configured for authentication, but server is not');
            return '';
        }
        if (Date.now() >= this.oidcAuth.getExpiresAt()) {
            await this.oidcAuth.refresh(localConfig);
        }
        return this.oidcAuth.getAccessToken();
    };
    getDetails = async () => ({
        host: new URL(this.host).host, // removes default port
        bearerToken: this.authEnabled ? await this.login().then((token) => `Bearer ${token}`) : undefined,
        headers: this.headers,
    });
}
exports.default = ConnectionREST;
__exportStar(require("./auth.js"), exports);
const fetchWithTimeout = (input, timeout, init) => {
    const controller = new AbortController();
    // Set a timeout to abort the request
    const timeoutId = setTimeout(() => controller.abort(), timeout * 1000);
    return fetch(input, { ...init, signal: controller.signal })
        .catch((error) => {
        if ((0, abort_controller_x_1.isAbortError)(error)) {
            throw new errors_js_1.WeaviateRequestTimeoutError(`Request timed out after ${timeout}ms`);
        }
        throw error; // For other errors, rethrow them
    })
        .finally(() => clearTimeout(timeoutId));
};
const httpClient = (config) => {
    const version = '/v1';
    const baseUri = `${config.host}${version}`;
    const url = makeUrl(baseUri);
    return {
        close: () => config.agent?.destroy(),
        post: (path, payload, expectReturnContent, bearerToken) => {
            const request = {
                method: 'POST',
                headers: {
                    ...config.headers,
                    'content-type': 'application/json',
                    ...getAuthHeaders(config, bearerToken),
                    ...(0, exports.getClientVersionHeader)(),
                },
                body: JSON.stringify(payload),
                agent: config.agent,
            };
            return fetchWithTimeout(url(path), config.timeout?.insert || 90, request).then(checkStatus(expectReturnContent));
        },
        put: (path, payload, expectReturnContent = true, bearerToken = '') => {
            const request = {
                method: 'PUT',
                headers: {
                    ...config.headers,
                    'content-type': 'application/json',
                    ...getAuthHeaders(config, bearerToken),
                    ...(0, exports.getClientVersionHeader)(),
                },
                body: JSON.stringify(payload),
                agent: config.agent,
            };
            return fetchWithTimeout(url(path), config.timeout?.insert || 90, request).then(checkStatus(expectReturnContent));
        },
        patch: (path, payload, bearerToken = '') => {
            const request = {
                method: 'PATCH',
                headers: {
                    ...config.headers,
                    'content-type': 'application/json',
                    ...getAuthHeaders(config, bearerToken),
                    ...(0, exports.getClientVersionHeader)(),
                },
                body: JSON.stringify(payload),
                agent: config.agent,
            };
            return fetchWithTimeout(url(path), config.timeout?.insert || 90, request).then(checkStatus(false));
        },
        delete: (path, payload = null, expectReturnContent = false, bearerToken = '') => {
            const request = {
                method: 'DELETE',
                headers: {
                    ...config.headers,
                    'content-type': 'application/json',
                    ...getAuthHeaders(config, bearerToken),
                    ...(0, exports.getClientVersionHeader)(),
                },
                body: payload ? JSON.stringify(payload) : undefined,
                agent: config.agent,
            };
            return fetchWithTimeout(url(path), config.timeout?.insert || 90, request).then(checkStatus(expectReturnContent));
        },
        head: (path, payload = null, bearerToken = '') => {
            const request = {
                method: 'HEAD',
                headers: {
                    ...config.headers,
                    'content-type': 'application/json',
                    ...getAuthHeaders(config, bearerToken),
                    ...(0, exports.getClientVersionHeader)(),
                },
                body: payload ? JSON.stringify(payload) : undefined,
                agent: config.agent,
            };
            return fetchWithTimeout(url(path), config.timeout?.query || 30, request).then(handleHeadResponse(false));
        },
        get: (path, expectReturnContent = true, bearerToken = '') => {
            const request = {
                method: 'GET',
                headers: {
                    ...config.headers,
                    ...getAuthHeaders(config, bearerToken),
                    ...(0, exports.getClientVersionHeader)(),
                },
                agent: config.agent,
            };
            return fetchWithTimeout(url(path), config.timeout?.query || 30, request).then(checkStatus(expectReturnContent));
        },
        getRaw: (path, bearerToken = '') => {
            // getRaw does not handle the status leaving this to the caller
            const request = {
                method: 'GET',
                headers: {
                    ...config.headers,
                    ...getAuthHeaders(config, bearerToken),
                    ...(0, exports.getClientVersionHeader)(),
                },
                agent: config.agent,
            };
            return fetchWithTimeout(url(path), config.timeout?.query || 30, request);
        },
        externalGet: (externalUrl) => {
            return fetch(externalUrl, {
                method: 'GET',
                headers: {
                    ...config.headers,
                },
            }).then(checkStatus(true));
        },
        externalPost: (externalUrl, body, contentType) => {
            if (contentType == undefined || contentType == '') {
                contentType = 'application/json';
            }
            const request = {
                body: undefined,
                method: 'POST',
                headers: {
                    ...config.headers,
                    'content-type': contentType,
                },
            };
            if (body != null) {
                request.body = body;
            }
            return fetch(externalUrl, request).then(checkStatus(true));
        },
    };
};
exports.httpClient = httpClient;
const makeUrl = (basePath) => (path) => basePath + path;
const checkStatus = (expectResponseBody) => (res) => {
    if (res.status >= 400) {
        return res.text().then((errText) => {
            let err;
            try {
                // in case of invalid json response (like empty string)
                err = JSON.stringify(JSON.parse(errText));
            }
            catch (e) {
                err = errText;
            }
            if (res.status === 401) {
                return Promise.reject(new errors_js_1.WeaviateUnauthenticatedError(err));
            }
            else if (res.status === 403) {
                return Promise.reject(new errors_js_1.WeaviateInsufficientPermissionsError(403, err));
            }
            else {
                return Promise.reject(new errors_js_1.WeaviateUnexpectedStatusCodeError(res.status, err));
            }
        });
    }
    if (expectResponseBody) {
        return res.json();
    }
    return Promise.resolve(undefined);
};
const handleHeadResponse = (expectResponseBody) => (res) => {
    if (res.status == 200 || res.status == 204 || res.status == 404) {
        return Promise.resolve(res.status == 200 || res.status == 204);
    }
    return checkStatus(expectResponseBody)(res);
};
const getAuthHeaders = (config, bearerToken) => bearerToken
    ? {
        Authorization: `Bearer ${bearerToken}`,
        'X-Weaviate-Cluster-Url': config.host,
    }
    : undefined;
const getClientVersionHeader = () => {
    return {
        'X-Weaviate-Client': `weaviate-client-typescript/${version_js_1.WEAVIATE_CLIENT_VERSION}`,
    };
};
exports.getClientVersionHeader = getClientVersionHeader;
