import { ContextRegistry } from "../engine_context_registry.js";
import { isLocalNetwork } from "../engine_networking_utils.js";
import { getParam } from "../engine_utils.js";

const debug = getParam("debugdebug");
let hide = false;
if (getParam("noerrors") || getParam("nooverlaymessages")) hide = true;

const globalErrorContainerKey = "needle_engine_global_error_container";

export enum LogType {
    Log,
    Warn,
    Error
}

export function getErrorCount() {
    return _errorCount;
}

const _errorListeners = new Array<(...args: any[]) => void>();
/** Register callback when a new error happens */
export function onError(cb: (...args: any[]) => void) { _errorListeners.push(cb); }
/** Unregister error callback */
export function offError(cb: (...args: any[]) => void) { _errorListeners.splice(_errorListeners.indexOf(cb), 1); }
let isInvokingErrorListeners = false;
function invokeErrorListeners(...args: any[]) {
    if (isInvokingErrorListeners) return; // prevent infinite loop
    isInvokingErrorListeners = true;
    try {
        for (let i = 0; i < _errorListeners.length; i++) {
            _errorListeners[i](...args);
        }
    }
    catch (e) {
        console.error(e);
    }
    isInvokingErrorListeners = false;
}

const originalConsoleError = console.error;
const patchedConsoleError = function (...args: any[]) {
    originalConsoleError.apply(console, args);
    onParseError(args);
    addLog(LogType.Error, args, null, null);
    onReceivedError(...args);
}



/** Set false to prevent overlay messages from being shown */
export function setAllowBalloonMessages(allow: boolean) {
    hide = !allow;
    if (allow) console.error = patchedConsoleError;
    else console.error = originalConsoleError;
}
/**
 * @deprecated Use {@link setAllowBalloonMessages} instead
 */
export function setAllowOverlayMessages(allow: boolean) {
    return setAllowBalloonMessages(allow);
}

export function makeErrorsVisibleForDevelopment() {
    if (hide) return;
    if (debug)
        console.warn("Patch console", window.location.hostname);
    console.error = patchedConsoleError;
    window.addEventListener("error", (event) => {
        if (!event) return;
        const message = event.error;
        if (message === undefined) {
            if (isLocalNetwork())
                console.warn("Received unknown error", event, event.target);
            return;
        }
        addLog(LogType.Error, message, event.filename, event.lineno);
        onReceivedError(event);
    }, true);
    window.addEventListener("unhandledrejection", (event) => {
        if (hide) return;
        if (!event) return;
        if (event.reason)
            addLog(LogType.Error, event.reason.message, event.reason.stack);
        else
            addLog(LogType.Error, "unhandled rejection");
        onReceivedError(event);
    });
}


let _errorCount = 0;

function onReceivedError(...args: any[]) {
    _errorCount += 1;
    invokeErrorListeners(...args);
}

function onParseError(args: Array<any>) {
    if (Array.isArray(args)) {
        for (let i = 0; i < args.length; i++) {
            const arg = args[i];
            if (typeof arg === "string" && arg.startsWith("THREE.PropertyBinding: Trying to update node for track:")) {
                args[i] = "Some animated objects couldn't be found: see console for details";
            }
        }
    }
}

export function addLog(type: LogType, message: string | any[], _file?: string | null, _line?: number | null) {
    if (hide) return;
    const context = ContextRegistry.Current;
    const domElement = context?.domElement ?? document.querySelector("needle-engine");
    if (!domElement) return;
    if (Array.isArray(message)) {
        let newMessage = "";
        for (let i = 0; i < message.length; i++) {
            let msg = message[i];
            if (msg instanceof Error) {
                msg = msg.message;
            }
            if (typeof msg === "object") continue;
            if (i > 0) newMessage += " ";
            newMessage += msg;
        }
        message = newMessage;
    }
    if (!message || message.length <= 0) return;
    showMessage(type, domElement, message);
}

// function getLocation(err: Error): string {
//     const location = err.stack;
//     console.log(location);
//     if (location) {
//         locationRegex.exec(location);
//         const match = locationRegex.exec(location);
//         return match ? match[1] : "";
//     }
//     return "";
// }

/**
 * 
 */
const currentMessages = new Map<string, Function>();


function showMessage(type: LogType, element: HTMLElement, msg: string | null | undefined) {
    if (msg === null || msg === undefined) return;

    const container = getLogsContainer(element);
    if (container.childElementCount >= 20) {
        const last = container.lastElementChild;
        returnMessageContainer(last as HTMLElement);
    }
    // truncate long messages before they go into the cache/set
    if (msg.length > 400) msg = msg.substring(0, 400) + "...";

    const key = msg;

    if (currentMessages.has(key)) {
        return;
    }

    const msgcontainer = getMessageContainer(type, msg);
    container.prepend(msgcontainer);

    const removeFunction = () => {
        currentMessages.delete(key);
        returnMessageContainer(msgcontainer);
    };
    currentMessages.set(key, removeFunction);

    setTimeout(removeFunction, 10_000);
}

/**
 * Clear all overlay messages from the screen
 */
export function clearMessages() {
    if (debug) console.log("Clearing messages");
    for (const cur of currentMessages.values()) {
        cur?.call(cur);
    }
    currentMessages.clear();
}


const logsContainerStyles = `

@import url('https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wght@8..144,100..1000&display=swap');

div[data-needle_engine_debug_overlay] {
    font-family: 'Roboto Flex', sans-serif;
    font-weight: 400;
    font-size: 16px;
}

div[data-needle_engine_debug_overlay] strong {
    font-weight: 700;
}

div[data-needle_engine_debug_overlay] a {
    color: white;
    text-decoration: none;
    border-bottom: 1px solid rgba(255, 255, 255, 0.3);
}

div[data-needle_engine_debug_overlay] a:hover {
    text-decoration: none;
    border: none;
}

div[data-needle_engine_debug_overlay] .log strong {
    color: rgba(200,200,200,.9);
}

div[data-needle_engine_debug_overlay] .warn strong {
    color: rgba(255,255,230, 1);
}

div[data-needle_engine_debug_overlay] .error strong {
    color: rgba(255,100,120, 1);
}
`;

function getLogsContainer(domElement: HTMLElement): HTMLElement {
    if (!globalThis[globalErrorContainerKey]) {
        globalThis[globalErrorContainerKey] = new Map<HTMLElement, HTMLElement>();
    }
    const errorsMap = globalThis[globalErrorContainerKey] as Map<HTMLElement, HTMLElement>;
    if (errorsMap.has(domElement)) {
        return errorsMap.get(domElement)!;
    } else {
        const container = document.createElement("div");
        errorsMap.set(domElement, container);
        container.setAttribute("data-needle_engine_debug_overlay", "");
        container.classList.add("debug-container");
        container.style.cssText = `
            position: absolute;
            top: 0;
            right: 5px;
            padding-top: 0px;
            max-width: 70%;
            max-height: calc(100% - 5px);
            z-index: 9999999999;
            pointer-events: scroll;
            display: flex;
            align-items: end;
            flex-direction: column;
            color: white;
            overflow: auto;
            word-break: break-word;
        `
        if (domElement.shadowRoot)
            domElement.shadowRoot.appendChild(container);
        else domElement.appendChild(container);

        const style = document.createElement("style");
        style.innerHTML = logsContainerStyles;
        container.appendChild(style);
        return container;
    }
}


const typeSymbol = Symbol("logtype");
const containerCache = new Map<LogType, HTMLElement[]>();

function returnMessageContainer(container: HTMLElement) {
    container.remove();
    const type = container[typeSymbol];
    const containers = containerCache.get(type) ?? [];
    containers.push(container);
    containerCache.set(type, containers);
}

function getMessageContainer(type: LogType, msg: string): HTMLElement {
    if (containerCache.has(type)) {
        const containers = containerCache.get(type)!;
        if (containers.length > 0) {
            const container = containers.pop()!;
            container.innerHTML = msg;
            return container;
        }
    }
    const element = document.createElement("div");
    element.setAttribute("data-id", "__needle_engine_debug_overlay");
    element.style.marginRight = "5px";
    element.style.padding = ".5em";
    element.style.backgroundColor = "rgba(0,0,0,.9)";
    element.style.marginTop = "5px";
    element.style.marginBottom = "3px";
    element.style.borderRadius = "8px";
    element.style.pointerEvents = "all";
    element.style.userSelect = "text";
    element.style.maxWidth = "250px";
    element.style.whiteSpace = "pre-wrap";
    element.style["backdrop-filter"] = "blur(10px)";
    element.style["-webkit-backdrop-filter"] = "blur(10px)";
    element.style.backgroundColor = "rgba(20,20,20,.8)";
    element.style.boxShadow = "inset 0 0 80px rgba(0,0,0,.2), 0 0 5px rgba(0,0,0,.2)";
    element.style.border = "1px solid rgba(160,160,160,.2)";
    element[typeSymbol] = type;
    switch (type) {
        case LogType.Log:
            element.classList.add("log");
            element.style.color = "rgba(200,200,200,.7)";
            element.style.backgroundColor = "rgba(40,40,40,.7)";
            // element.style.backgroundColor = "rgba(200,200,200,.5)";
            break;
        case LogType.Warn:
            element.classList.add("warn");
            element.style.color = "rgb(255, 255, 150)";
            element.style.backgroundColor = "rgba(50,50,20,.8)";
            // element.style.backgroundColor = "rgba(245,245,50,.5)";
            break;
        case LogType.Error:
            element.classList.add("error");
            element.style.color = "rgb(255, 50, 50";
            element.style.backgroundColor = "rgba(50,20,20,.8)";
            // element.style.backgroundColor = "rgba(255,50,50,.5)";
            break;
    }
    element.title = "Open the browser console (F12) for more information";

    // msg = msg.replace(/[\n\r]/g, "<br/>");
    // console.log(msg);
    element.innerHTML = msg;

    return element;
}