import { ERouteHttpVerb } from "@devowl-wp/api";

import type { commonRequest } from "./commonRequest.js";

declare global {
    interface Window {
        /**
         * This number indicates the failed `GET` requests for all REST API calls.
         * See also `commonRequest.tsx`.
         */
        detectCorruptRestApiFailed: number;
    }
}

const NOTICE_ID = "notice-corrupt-rest-api";
const NOTICE_ATTRIBUTE_NAMESPACE = "data-namespace";

async function addToNotice(namespace: string, onlyWhen = async () => {}) {
    const notice = document.getElementById(NOTICE_ID);

    // Only in backend and when a corrupt REST API detected
    // Completely ignore the offline state of the browser as this could happen when the
    // PC was in energy-save mode and started again
    if (notice && window.navigator.onLine) {
        // Already shown as "defect"?
        if (notice.querySelector(`li[${NOTICE_ATTRIBUTE_NAMESPACE}="${namespace}"]`)) {
            return;
        }

        try {
            await onlyWhen();
        } catch (e) {
            notice.style.display = "block";
            const li = document.createElement("li");
            li.setAttribute(NOTICE_ATTRIBUTE_NAMESPACE, namespace);
            li.innerHTML = `<code>${namespace}</code>`;
            notice.childNodes[1].appendChild(li);
            notice.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
        }
    }
}

async function removeCorruptRestApi(namespace: string) {
    const notice = document.getElementById(NOTICE_ID);
    if (notice) {
        const namespaceLine = notice.querySelector(`li[${NOTICE_ATTRIBUTE_NAMESPACE}="${namespace}"]`);
        namespaceLine?.remove();

        // Hide if necessary
        if (!notice.childNodes[1].childNodes.length) {
            notice.style.display = "none";

            // Reset complete log
            const textarea = notice.querySelector("textarea");
            if (textarea) {
                textarea.value = "";
            }
        }
    }
}

/**
 * Set a request as failing so the endpoint is probably corrupt (see `corruptRestApi.tsx`)
 *
 * Please consider the following:
 *
 * > The fetch() function will automatically throw an error for network errors but not for HTTP errors such as 4xx or 5xx responses.
 *
 * @see https://jasonwatmore.com/post/2021/10/09/fetch-error-handling-for-failed-http-responses-and-network-errors
 */
function addCorruptRestApi(
    { method }: Parameters<typeof commonRequest>[0]["settings"],
    addNamespaceImmediate?: string,
) {
    if (method === ERouteHttpVerb.GET) {
        if (addNamespaceImmediate) {
            addToNotice(addNamespaceImmediate, () => {
                throw new Error();
            });
        } else {
            window.detectCorruptRestApiFailed = (window.detectCorruptRestApiFailed || 0) + 1;
            window.dispatchEvent(new CustomEvent(NOTICE_ID));
        }
    }
}

function addCorruptRestApiLog({
    route,
    method,
    ms,
    response,
}: {
    route: string;
    method: ERouteHttpVerb;
    ms: number;
    response: string;
}) {
    const textarea = document.querySelector(`#${NOTICE_ID} textarea`) as HTMLTextAreaElement;
    if (textarea) {
        const currentValue = textarea.value.split("\n").slice(0, 9);
        currentValue.unshift(
            `[${new Date().toLocaleTimeString()}] [${method || "GET"}] [${ms}ms] ${route}; ${response?.substr(0, 999)}`,
        );
        textarea.value = currentValue.join("\n");
    }
}

/**
 * Register a new endpoint which needs to resolve to a valid JSON result. In this way we
 * can detect a corrupt REST API namespace e. g. it is blocked through a security plugin.
 *
 * This function needs to be called in `document.readyState < completed`!
 */
function handleCorruptRestApi(resolve: Record<string, () => Promise<void>>) {
    // Initially set
    window.detectCorruptRestApiFailed = window.detectCorruptRestApiFailed || 0;

    // Hide false-positives of previous failed REST requests which occurred due to page switch and bfcache (see CU-33tce0y)
    window.addEventListener("pageshow", ({ persisted }) => {
        const notice = document.getElementById(NOTICE_ID);
        if (notice && persisted && window.detectCorruptRestApiFailed === 0) {
            notice.style.display = "none";
        }
    });

    const fnCheck = async () => {
        // Only in backend and when a corrupt REST API detected
        if (window.detectCorruptRestApiFailed > 0) {
            for (const namespace of Object.keys(resolve)) {
                addToNotice(namespace, resolve[namespace]);
            }
        }
    };

    let checkTimeout: ReturnType<typeof setTimeout>;
    const fnThrottled = () => {
        clearTimeout(checkTimeout);
        checkTimeout = setTimeout(fnCheck, 1000);
    };

    fnThrottled();
    window.addEventListener(NOTICE_ID, fnThrottled);
}

/**
 * @deprecated For backwards-compatibility.
 */
const handleCorrupRestApi = handleCorruptRestApi;

export { addCorruptRestApi, addCorruptRestApiLog, removeCorruptRestApi, handleCorruptRestApi, handleCorrupRestApi };
