/*
 * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.
 */
import { SDKHooks } from "../hooks/hooks.js";
import { ConnectionError, InvalidRequestError, RequestAbortedError, RequestTimeoutError, UnexpectedClientError, } from "../models/errors/httpclienterrors.js";
import { stringToBase64 } from "./base64.js";
import { SDK_METADATA, serverURLFromOptions } from "./config.js";
import { encodeForm } from "./encodings.js";
import { env, fillGlobals } from "./env.js";
import { HTTPClient, isAbortError, isConnectionError, isTimeoutError, matchContentType, matchStatusCode, } from "./http.js";
import { ERR, OK } from "./result.js";
import { retry } from "./retries.js";
const gt = typeof globalThis === "undefined" ? null : globalThis;
const webWorkerLike = typeof gt === "object"
    && gt != null
    && "importScripts" in gt
    && typeof gt["importScripts"] === "function";
const isBrowserLike = webWorkerLike
    || (typeof navigator !== "undefined" && "serviceWorker" in navigator)
    || (typeof window === "object" && typeof window.document !== "undefined");
export class ClientSDK {
    #httpClient;
    #hooks;
    #logger;
    _baseURL;
    _options;
    constructor(options = {}) {
        const opt = options;
        if (typeof opt === "object"
            && opt != null
            && "hooks" in opt
            && opt.hooks instanceof SDKHooks) {
            this.#hooks = opt.hooks;
        }
        else {
            this.#hooks = new SDKHooks();
        }
        const defaultHttpClient = new HTTPClient();
        options.httpClient = options.httpClient || defaultHttpClient;
        options = this.#hooks.sdkInit(options);
        const url = serverURLFromOptions(options);
        if (url) {
            url.pathname = url.pathname.replace(/\/+$/, "") + "/";
        }
        this._baseURL = url;
        this.#httpClient = options.httpClient || defaultHttpClient;
        this._options = { ...fillGlobals(options), hooks: this.#hooks };
        this.#logger = this._options.debugLogger;
        if (!this.#logger && env().CLOUDINARY_DEBUG) {
            this.#logger = console;
        }
    }
    _createRequest(context, conf, options) {
        const { method, path, query, headers: opHeaders, security } = conf;
        const base = conf.baseURL ?? this._baseURL;
        if (!base) {
            return ERR(new InvalidRequestError("No base URL provided for operation"));
        }
        const reqURL = new URL(base);
        const inputURL = new URL(path, reqURL);
        if (path) {
            reqURL.pathname += reqURL.pathname.endsWith("/") ? "" : "/";
            reqURL.pathname += inputURL.pathname.replace(/^\/+/, "");
        }
        let finalQuery = query || "";
        const secQuery = [];
        for (const [k, v] of Object.entries(security?.queryParams || {})) {
            const q = encodeForm(k, v, { charEncoding: "percent" });
            if (typeof q !== "undefined") {
                secQuery.push(q);
            }
        }
        if (secQuery.length) {
            finalQuery += `&${secQuery.join("&")}`;
        }
        if (finalQuery) {
            const q = finalQuery.startsWith("&") ? finalQuery.slice(1) : finalQuery;
            reqURL.search = `?${q}`;
        }
        const headers = new Headers(opHeaders);
        const username = security?.basic.username;
        const password = security?.basic.password;
        if (username != null || password != null) {
            const encoded = stringToBase64([username || "", password || ""].join(":"));
            headers.set("Authorization", `Basic ${encoded}`);
        }
        const securityHeaders = new Headers(security?.headers || {});
        for (const [k, v] of securityHeaders) {
            headers.set(k, v);
        }
        let cookie = headers.get("cookie") || "";
        for (const [k, v] of Object.entries(security?.cookies || {})) {
            cookie += `; ${k}=${v}`;
        }
        cookie = cookie.startsWith("; ") ? cookie.slice(2) : cookie;
        headers.set("cookie", cookie);
        const userHeaders = new Headers(options?.headers ?? options?.fetchOptions?.headers);
        for (const [k, v] of userHeaders) {
            headers.set(k, v);
        }
        // Only set user agent header in non-browser-like environments since CORS
        // policy disallows setting it in browsers e.g. Chrome throws an error.
        if (!isBrowserLike) {
            headers.set(conf.uaHeader ?? "user-agent", conf.userAgent ?? SDK_METADATA.userAgent);
        }
        const fetchOptions = {
            ...options?.fetchOptions,
            ...options,
        };
        if (!fetchOptions?.signal && conf.timeoutMs && conf.timeoutMs > 0) {
            const timeoutSignal = AbortSignal.timeout(conf.timeoutMs);
            fetchOptions.signal = timeoutSignal;
        }
        if (conf.body instanceof ReadableStream) {
            Object.assign(fetchOptions, { duplex: "half" });
        }
        let input;
        try {
            input = this.#hooks.beforeCreateRequest(context, {
                url: reqURL,
                options: {
                    ...fetchOptions,
                    body: conf.body ?? null,
                    headers,
                    method,
                },
            });
        }
        catch (err) {
            return ERR(new UnexpectedClientError("Create request hook failed to execute", {
                cause: err,
            }));
        }
        return OK(new Request(input.url, input.options));
    }
    async _do(request, options) {
        const { context, errorCodes } = options;
        return retry(async () => {
            const req = await this.#hooks.beforeRequest(context, request.clone());
            await logRequest(this.#logger, req).catch((e) => this.#logger?.log("Failed to log request:", e));
            let response = await this.#httpClient.request(req);
            try {
                if (matchStatusCode(response, errorCodes)) {
                    const result = await this.#hooks.afterError(context, response, null);
                    if (result.error) {
                        throw result.error;
                    }
                    response = result.response || response;
                }
                else {
                    response = await this.#hooks.afterSuccess(context, response);
                }
            }
            finally {
                await logResponse(this.#logger, response, req)
                    .catch(e => this.#logger?.log("Failed to log response:", e));
            }
            return response;
        }, { config: options.retryConfig, statusCodes: options.retryCodes }).then((r) => OK(r), (err) => {
            switch (true) {
                case isAbortError(err):
                    return ERR(new RequestAbortedError("Request aborted by client", {
                        cause: err,
                    }));
                case isTimeoutError(err):
                    return ERR(new RequestTimeoutError("Request timed out", { cause: err }));
                case isConnectionError(err):
                    return ERR(new ConnectionError("Unable to make request", { cause: err }));
                default:
                    return ERR(new UnexpectedClientError("Unexpected HTTP client error", {
                        cause: err,
                    }));
            }
        });
    }
}
const jsonLikeContentTypeRE = /^(application|text)\/([^+]+\+)*json.*/;
const jsonlLikeContentTypeRE = /^(application|text)\/([^+]+\+)*(jsonl|x-ndjson)\b.*/;
async function logRequest(logger, req) {
    if (!logger) {
        return;
    }
    const contentType = req.headers.get("content-type");
    const ct = contentType?.split(";")[0] || "";
    logger.group(`> Request: ${req.method} ${req.url}`);
    logger.group("Headers:");
    for (const [k, v] of req.headers.entries()) {
        logger.log(`${k}: ${v}`);
    }
    logger.groupEnd();
    logger.group("Body:");
    switch (true) {
        case jsonLikeContentTypeRE.test(ct):
            logger.log(await req.clone().json());
            break;
        case ct.startsWith("text/"):
            logger.log(await req.clone().text());
            break;
        case ct === "multipart/form-data": {
            const body = await req.clone().formData();
            for (const [k, v] of body) {
                const vlabel = v instanceof Blob ? "<Blob>" : v;
                logger.log(`${k}: ${vlabel}`);
            }
            break;
        }
        default:
            logger.log(`<${contentType}>`);
            break;
    }
    logger.groupEnd();
    logger.groupEnd();
}
async function logResponse(logger, res, req) {
    if (!logger) {
        return;
    }
    const contentType = res.headers.get("content-type");
    const ct = contentType?.split(";")[0] || "";
    logger.group(`< Response: ${req.method} ${req.url}`);
    logger.log("Status Code:", res.status, res.statusText);
    logger.group("Headers:");
    for (const [k, v] of res.headers.entries()) {
        logger.log(`${k}: ${v}`);
    }
    logger.groupEnd();
    logger.group("Body:");
    switch (true) {
        case matchContentType(res, "application/json")
            || jsonLikeContentTypeRE.test(ct) && !jsonlLikeContentTypeRE.test(ct):
            logger.log(await res.clone().json());
            break;
        case matchContentType(res, "application/jsonl")
            || jsonlLikeContentTypeRE.test(ct):
        case matchContentType(res, "text/event-stream"):
            logger.log(`<${contentType}>`);
            break;
        case matchContentType(res, "text/*"):
            logger.log(await res.clone().text());
            break;
        case matchContentType(res, "multipart/form-data"): {
            const body = await res.clone().formData();
            for (const [k, v] of body) {
                const vlabel = v instanceof Blob ? "<Blob>" : v;
                logger.log(`${k}: ${vlabel}`);
            }
            break;
        }
        default:
            logger.log(`<${contentType}>`);
            break;
    }
    logger.groupEnd();
    logger.groupEnd();
}
//# sourceMappingURL=sdks.js.map