import rxjs, { ajax } from "./rx.js";
import { AjaxError } from "./error.js";
import { isSDK, urlSDK } from "../helpers/sdk.js";

export default function(opts) {
    if (typeof opts === "string") opts = { url: opts, withCredentials: true };
    else if (typeof opts !== "object") throw new Error("unsupported call");

    if (!opts.headers) opts.headers = {};
    if (!opts.responseType) opts.responseType = "text";
    opts.headers["X-Requested-With"] = "XmlHttpRequest";
    if (window.BEARER_TOKEN) opts.headers["Authorization"] = `Bearer ${window.BEARER_TOKEN}`;

    if (opts.url.startsWith("data:")) return rxjs.of({ response: parseDataUrl(opts.url) });
    if (isSDK()) {
        if (["/api/config"].indexOf(opts.url) === -1) opts.withCredentials = false;
        opts.url = urlSDK(opts.url);
    }

    const responseType = opts.responseType === "json" ? "text" : opts.responseType;
    return ajax({
        withCredentials: true,
        ...opts,
        responseType,
    }).pipe(
        rxjs.map((res) => {
            if (opts.responseType === "json") {
                const result = res.xhr.responseText;
                res.responseJSON = JSON.parse(result);
                if (res.responseJSON.status !== "ok") {
                    throw new AjaxError("Oups something went wrong", result);
                }
            }
            return res;
        }),
        rxjs.catchError(
            (err) => activePage
                ? rxjs.throwError(processError(err.xhr, err))
                : rxjs.EMPTY
        ),
    );
}

let activePage = true;
window.addEventListener("beforeunload", function() {
    activePage = false;
});

function parseDataUrl(url) {
    const matches = url.match(/^data:(.*?)(;base64)?,(.*)$/);
    if (!matches) throw new Error("Invalid Data URL");

    const isBase64 = !!matches[2];
    const data = matches[3];
    if (isBase64) {
        const binaryString = atob(data);
        const len = binaryString.length;
        const bytes = new Uint8Array(len);
        for (let i = 0; i < len; i++) {
            bytes[i] = binaryString.charCodeAt(i);
        }
        return bytes.buffer;
    }
    const decodedData = decodeURIComponent(data);
    const encoder = new TextEncoder();
    return encoder.encode(decodedData).buffer;
}

function processError(xhr, err) {
    let responseText = "";
    try {
        responseText = xhr?.responseText;
        // InvalidStateError: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'arraybuffer').
    } catch (err) {}

    const message = (function(content) {
        try {
            return JSON.parse(content).message;
        } catch (err) {
            return Array.from(new Set(
                content.replace(/<[^>]*>/g, "")
                    .replace(/\n{2,}/, "\n")
                    .trim()
                    .split("\n")
            )).join(" ");
        }
    })(responseText);

    if (window.navigator.onLine === false) {
        return new AjaxError("Connection Lost", err, "NO_INTERNET");
    }
    switch (parseInt(xhr?.status)) {
    case 500:
        return new AjaxError(
            message || "Oups something went wrong with our servers",
            err, "INTERNAL_SERVER_ERROR"
        );
    case 401:
        return new AjaxError(
            message || "Authentication error",
            err, "Unauthorized"
        );
    case 403:
        return new AjaxError(
            message || "You can't do that",
            err, "FORBIDDEN"
        );
    case 413:
        return new AjaxError(
            message || "Payload too large",
            err, "PAYLOAD_TOO_LARGE"
        );
    case 502:
        return new AjaxError(
            message || "The destination is acting weird",
            err, "BAD_GATEWAY"
        );
    case 409:
        return new AjaxError(
            message || "Oups you just ran into a conflict",
            err, "CONFLICT"
        );
    case 0:
        switch (responseText) {
        case "":
            return new AjaxError(
                "Service unavailable, if the problem persist, contact your administrator",
                err, "INTERNAL_SERVER_ERROR"
            );
        default:
            return new AjaxError(responseText, err, "INTERNAL_SERVER_ERROR");
        }
    default:
        return new AjaxError(message || "Oups something went wrong", err);
    }
}
