import { UpstashError } from "./error.js";
export class HttpClient {
    constructor(config) {
        Object.defineProperty(this, "baseUrl", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "headers", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "options", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "retry", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.options = {
            backend: config.options?.backend,
            agent: config.agent,
            responseEncoding: config.responseEncoding ?? "base64",
            cache: config.cache,
        };
        this.baseUrl = config.baseUrl.replace(/\/$/, "");
        this.headers = {
            "Content-Type": "application/json",
            ...config.headers,
        };
        if (this.options.responseEncoding === "base64") {
            this.headers["Upstash-Encoding"] = "base64";
        }
        if (typeof config?.retry === "boolean" && config?.retry === false) {
            this.retry = {
                attempts: 1,
                backoff: () => 0,
            };
        }
        else {
            this.retry = {
                attempts: config?.retry?.retries ?? 5,
                backoff: config?.retry?.backoff ??
                    ((retryCount) => Math.exp(retryCount) * 50),
            };
        }
    }
    mergeTelemetry(telemetry) {
        function merge(obj, key, value) {
            if (!value) {
                return obj;
            }
            if (obj[key]) {
                obj[key] = [obj[key], value].join(",");
            }
            else {
                obj[key] = value;
            }
            return obj;
        }
        this.headers = merge(this.headers, "Upstash-Telemetry-Runtime", telemetry.runtime);
        this.headers = merge(this.headers, "Upstash-Telemetry-Platform", telemetry.platform);
        this.headers = merge(this.headers, "Upstash-Telemetry-Sdk", telemetry.sdk);
    }
    async request(req) {
        const requestOptions = {
            cache: this.options.cache,
            method: "POST",
            headers: this.headers,
            body: JSON.stringify(req.body),
            keepalive: true,
            agent: this.options?.agent,
            /**
             * Fastly specific
             */
            backend: this.options?.backend,
        };
        let res = null;
        let error = null;
        for (let i = 0; i <= this.retry.attempts; i++) {
            try {
                res = await fetch([this.baseUrl, ...(req.path ?? [])].join("/"), requestOptions);
                break;
            }
            catch (err) {
                error = err;
                await new Promise((r) => setTimeout(r, this.retry.backoff(i)));
            }
        }
        if (!res) {
            throw error ?? new Error("Exhausted all retries");
        }
        const body = (await res.json());
        if (!res.ok) {
            throw new UpstashError(body.error);
        }
        if (this.options?.responseEncoding === "base64") {
            return Array.isArray(body) ? body.map(decode) : decode(body);
        }
        return body;
    }
}
function base64decode(b64) {
    let dec = "";
    try {
        /**
         * Using only atob() is not enough because it doesn't work with unicode characters
         */
        const binString = atob(b64);
        const size = binString.length;
        const bytes = new Uint8Array(size);
        for (let i = 0; i < size; i++) {
            bytes[i] = binString.charCodeAt(i);
        }
        dec = new TextDecoder().decode(bytes);
    }
    catch {
        dec = b64;
    }
    return dec;
    // try {
    //   return decodeURIComponent(dec);
    // } catch {
    //   return dec;
    // }
}
function decode(raw) {
    let result = undefined;
    switch (typeof raw.result) {
        case "undefined":
            return raw;
        case "number": {
            result = raw.result;
            break;
        }
        case "object": {
            if (Array.isArray(raw.result)) {
                result = raw.result.map((v) => typeof v === "string"
                    ? base64decode(v)
                    : Array.isArray(v)
                        ? v.map(base64decode)
                        : v);
            }
            else {
                // If it's not an array it must be null
                // Apparently null is an object in javascript
                result = null;
            }
            break;
        }
        case "string": {
            result = raw.result === "OK" ? "OK" : base64decode(raw.result);
            break;
        }
        default:
            break;
    }
    return { result, error: raw.error };
}
