import { needleLogoOnlySVG } from "./assets/index.js"
import { isDevEnvironment, showBalloonWarning } from "./debug/index.js";
import { hasCommercialLicense, hasProLicense, runtimeLicenseCheckPromise } from "./engine_license.js";
import { Mathf } from "./engine_math.js";
import { LoadingProgressArgs } from "./engine_setup.js";
import { getParam } from "./engine_utils.js";

const debug = getParam("debugloading");
const debugRendering = getParam("debugloadingrendering");
const debugLicense = getParam("debuglicense");

declare type LoadingStyleOption = "dark" | "light" | "auto";

/** @internal */
export class LoadingElementOptions {
    className?: string;
    additionalClasses?: string[];
}

/** @internal */
export interface ILoadingViewHandler {
    onLoadingBegin(message?: string)
    onLoadingUpdate(progress: LoadingProgressArgs | number, message?: string);
    onLoadingFinished(message?: string);
    setMessage(string: string);
}

let currentFileProgress = 0;
let currentFileName: string;
/** @internal */
export function calculateProgress01(progress: LoadingProgressArgs) {
    if (debug) console.log(progress.progress.loaded.toFixed(0) + "/" + progress.progress.total.toFixed(0), progress);

    const count = progress.count;
    const total: number | undefined = progress.progress.total;
    // if the progress event total amount is unknown / not reported
    // we slowly move the progress bar forward
    if (total === 0 || total === undefined) {
        // reset the temp progress when the file has changed
        if (currentFileName !== progress.name)
            currentFileProgress = 0;
        currentFileName = progress.name;
        // slowly move the progress bar forward
        currentFileProgress += (1 - currentFileProgress) * .001;
        if (debug) showBalloonWarning("Loading " + progress.name + " did not report total size");
    }
    else {
        currentFileProgress = progress.progress.loaded / total;
    }
    const prog = progress.index / count + currentFileProgress / count;
    return Mathf.clamp01(prog);
}

/** @internal */
export class EngineLoadingView implements ILoadingViewHandler {

    static LoadingContainerClassName = "loading";

    // the raw progress
    loadingProgress: number = 0;

    /** Usually the NeedleEngineHTMLElement */
    private _element: HTMLElement;
    private _progress: number = 0;
    private _allowCustomLoadingElement: boolean = true;
    private _loadingElement?: HTMLElement;
    private _loadingTextContainer: HTMLElement | null = null;
    private _loadingBar: HTMLElement | null = null;
    private _messageContainer: HTMLElement | null = null;
    private _loadingElementOptions?: LoadingElementOptions;

    /**
     * Creates a new loading view
     * @param owner the element that will contain the loading view (should be the NeedleEngineHTMLElement)
     */
    constructor(owner: HTMLElement, opts?: LoadingElementOptions) {
        this._element = owner;
        this._loadingElementOptions = opts;
    }

    async onLoadingBegin(message?: string) {
        const _element = this._element.shadowRoot || this._element;
        if (debug) console.warn("Begin Loading")
        if (!this._loadingElement) {
            for (let i = 0; i < _element.children.length; i++) {
                const el = _element.children[i] as HTMLElement;
                if (el.classList.contains(EngineLoadingView.LoadingContainerClassName)) {
                    if (!this._allowCustomLoadingElement) {
                        if (debug) console.warn("Remove custom loading container")
                        _element.removeChild(el);
                        continue;
                    }
                    this._loadingElement = this.createLoadingElement(el);
                }
            }
            if (!this._loadingElement)
                this._loadingElement = this.createLoadingElement();
        }
        this._progress = 0;
        this.loadingProgress = 0;
        this._loadingElement.style.display = "flex";
        _element.appendChild(this._loadingElement);
        this.smoothProgressLoop();
        this.setMessage(message ?? "");
    }

    onLoadingUpdate(progress: LoadingProgressArgs | ProgressEvent | number, message?: string) {
        if (!this._loadingElement?.parentNode) {
            return;
        }
        // console.log(callback.name, callback.progress.loaded / callback.progress.total, callback.index + "/" + callback.count);
        let total01 = 0;
        if (typeof progress === "number") {
            total01 = progress;
        }
        else {
            if ("index" in progress)
                total01 = calculateProgress01(progress);
            if (!message && "name" in progress)
                this.setMessage("loading " + progress.name);
        }
        this.loadingProgress = total01;
        if (message) this.setMessage(message);
        this.updateDisplay();
    }

    onLoadingFinished() {
        if (debug)
            console.warn("Finished Loading");
        if (!debugRendering) {
            this.loadingProgress = 1;
            this.onDoneLoading();
        }
    }

    setMessage(message: string) {
        if (this._messageContainer) {
            this._messageContainer.innerText = message;
        }
    }

    private _progressLoop: any;
    private smoothProgressLoop() {
        if (this._progressLoop) return;
        let dt = 1 / 12;
        if (debugRendering) {
            dt = 1 / 500;
            if (typeof debugRendering === "number") dt *= debugRendering;
        }
        this._progressLoop = setInterval(() => {
            // increase loading speed when almost done
            if (this.loadingProgress >= .95 && !debugRendering) dt = .9;
            this._progress = Mathf.lerp(this._progress, this.loadingProgress, dt * this.loadingProgress);
            this.updateDisplay();
        }, dt);
    }

    private onDoneLoading() {
        if (this._loadingElement) {
            if (debug) console.log("Hiding loading element");
            this._loadingElement.style.display = "none";
            this._loadingElement.remove();
        }
        if (this._progressLoop)
            clearInterval(this._progressLoop);
        this._progressLoop = null;
    }

    private updateDisplay() {
        const t = this._progress;
        const percent = (t * 100).toFixed(0) + "%";
        if (this._loadingBar) {
            this._loadingBar.style.width = t * 100 + "%";
        }

        if (this._loadingTextContainer)
            this._loadingTextContainer.textContent = percent;
    }

    private createLoadingElement(existing?: HTMLElement) {
        if (debug && !existing) console.log("Creating loading element");
        this._loadingElement = existing || document.createElement("div");

        let loadingStyle: LoadingStyleOption = this._element.getAttribute("loading-style") as LoadingStyleOption;
        // if nothing is defined OR loadingStyle is set to auto
        if (!loadingStyle || loadingStyle === "auto") {
            if (window.matchMedia('(prefers-color-scheme: dark)').matches)
                loadingStyle = "dark";
            else
                loadingStyle = "light";
        }

        const hasLicense = hasProLicense();
        if (!existing) {
            this._loadingElement.style.position = "absolute";
            this._loadingElement.style.width = "100%";
            this._loadingElement.style.height = "100%";
            this._loadingElement.style.left = "0";
            this._loadingElement.style.top = "0";
            if (loadingStyle === "light")
                this._loadingElement.style.backgroundColor = "#ddd";
            else
                this._loadingElement.style.backgroundColor = "#222";
            this._loadingElement.style.display = "flex";
            this._loadingElement.style.alignItems = "center";
            this._loadingElement.style.justifyContent = "center";
            this._loadingElement.style.zIndex = Number.MAX_SAFE_INTEGER.toString();
            this._loadingElement.style.flexDirection = "column";
            this._loadingElement.style.pointerEvents = "none";
            this._loadingElement.style.color = "white";
            this._loadingElement.style.fontFamily = 'system-ui, Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"';
            this._loadingElement.style.fontSize = "1rem";
            if (loadingStyle === "light")
                this._loadingElement.style.color = "rgba(0,0,0,.6)";
            else
                this._loadingElement.style.color = "rgba(255,255,255,.3)";
            if (hasLicense && this._element) {
                const loadingBackgroundColor = this._element.getAttribute("loading-background-color");
                if (loadingBackgroundColor) {
                    this._loadingElement.style.backgroundColor = loadingBackgroundColor;
                }
                const textColor = this._element.getAttribute("loading-text-color");
                if (textColor) {
                    this._loadingElement.style.color = textColor;
                }
            }
        }

        const className = this._loadingElementOptions?.className ?? EngineLoadingView.LoadingContainerClassName;
        this._loadingElement.classList.add(className);
        if (this._loadingElementOptions?.additionalClasses) {
            for (const c of this._loadingElementOptions.additionalClasses) {
                this._loadingElement.classList.add(c);
            }
        }

        const content = document.createElement("div");
        this._loadingElement.appendChild(content);

        const logo = document.createElement("img");
        const logoSize = 120;
        logo.style.width = `${logoSize}px`;
        logo.style.height = `${logoSize}px`;
        logo.style.paddingTop = "20px";
        logo.style.paddingBottom = "10px";
        logo.style.margin = "0px";
        logo.style.userSelect = "none";
        logo.style.objectFit = "contain";
        logo.style.transition = "transform 1.5s ease-out, opacity .3s ease-in-out";
        logo.style.transform = "translateY(30px)";
        logo.style.opacity = "0.05";
        setTimeout(() => {
            logo.style.opacity = "1";
            logo.style.transform = "translateY(0px)";
        }, 1);
        logo.src = needleLogoOnlySVG;
        let isUsingCustomLogo = false;
        if (hasLicense && this._element) {
            const customLogo = this._element.getAttribute("loading-logo-src");
            if (customLogo) {
                isUsingCustomLogo = true;
                logo.src = customLogo;
            }
        }
        content.appendChild(logo);

        const details = document.createElement("div");
        details.style.cssText = `
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        width: 100%;
        opacity: 0;
        transition: opacity 1s ease-in-out 4s;
        `;
        setTimeout(() => { details.style.opacity = "1"; }, 1);
        this._loadingElement.appendChild(details);

        const loadingBarContainer = document.createElement("div");
        const maxWidth = 100;
        loadingBarContainer.style.display = "flex";
        loadingBarContainer.style.width = maxWidth + "%";
        loadingBarContainer.style.height = "3px";
        loadingBarContainer.style.position = "absolute";
        loadingBarContainer.style.left = "0";
        loadingBarContainer.style.bottom = "0px";
        loadingBarContainer.style.opacity = "0";
        loadingBarContainer.style.transition = "opacity 1s ease-in-out 2s";
        setTimeout(() => { loadingBarContainer.style.opacity = "1"; }, 1);
        if (loadingStyle === "light")
            loadingBarContainer.style.backgroundColor = "rgba(0,0,0,.2)"
        else
            loadingBarContainer.style.backgroundColor = "rgba(255,255,255,.2)"
        // loadingBarContainer.style.alignItems = "center";

        this._loadingElement.appendChild(loadingBarContainer);


        this._loadingBar = document.createElement("div");
        loadingBarContainer.appendChild(this._loadingBar);
        const getGradientPos = function (t: number): string {
            return Mathf.lerp(0, maxWidth, t) + "%";
        }
        this._loadingBar.style.background =
            `linear-gradient(90deg, #204f49 ${getGradientPos(0)}, #0BA398 ${getGradientPos(.3)}, #66A22F ${getGradientPos(.6)}, #D7DB0A ${getGradientPos(1)})`;
        this._loadingBar.style.backgroundAttachment = "fixed";
        this._loadingBar.style.width = "0%";
        this._loadingBar.style.height = "100%";
        if (hasLicense && this._element) {
            const primaryColor = this._element.getAttribute("primary-color");
            const secondaryColor = this._element.getAttribute("secondary-color");
            if (primaryColor && secondaryColor) {
                this._loadingBar.style.background = `linear-gradient(90deg, ${primaryColor} ${getGradientPos(0)}, ${secondaryColor} ${getGradientPos(1)})`;
            }
            else if (primaryColor) {
                this._loadingBar.style.background = primaryColor;
            }
            else if (secondaryColor) {
                this._loadingBar.style.background = secondaryColor;
            }
        }

        this._loadingTextContainer = document.createElement("div");
        this._loadingTextContainer.style.display = "flex";
        this._loadingTextContainer.style.justifyContent = "center";
        this._loadingTextContainer.style.marginTop = ".2rem";
        details.appendChild(this._loadingTextContainer);

        const messageContainer = document.createElement("div");
        this._messageContainer = messageContainer;
        messageContainer.style.display = "flex";
        messageContainer.style.fontSize = ".8rem";
        messageContainer.style.paddingTop = ".1rem";
        // messageContainer.style.border = "1px solid rgba(255,255,255,.1)";
        messageContainer.style.justifyContent = "center";
        details.appendChild(messageContainer);

        if (hasLicense && this._element) {
            const loadingTextColor = this._element.getAttribute("loading-text-color");
            if (loadingTextColor) {
                messageContainer.style.color = loadingTextColor;
            }
        }

        this.handleRuntimeLicense(this._loadingElement);

        return this._loadingElement;
    }

    private async handleRuntimeLicense(loadingElement: HTMLElement) {
        // First check if we have a commercial license
        let commercialLicense = hasCommercialLicense();
        // if it's the case then we don't need to perform a runtime check
        if (commercialLicense) return;

        // If we don't have a commercial license, then we need to display our message
        if (debugLicense) console.log("Loading UI has commercial license?", commercialLicense);
        const nonCommercialContainer = document.createElement("div");
        nonCommercialContainer.style.paddingTop = ".6em";
        nonCommercialContainer.style.fontSize = ".8em";
        nonCommercialContainer.style.textTransform = "uppercase";
        nonCommercialContainer.innerText = "NEEDLE ENGINE NON COMMERCIAL VERSION\nCLICK HERE TO GET A LICENSE";
        nonCommercialContainer.style.cursor = "pointer";
        nonCommercialContainer.style.userSelect = "none";
        nonCommercialContainer.style.textAlign = "center";
        nonCommercialContainer.style.pointerEvents = "all";
        nonCommercialContainer.addEventListener("click", () => window.open("https://needle.tools/pricing", "_self"));
        nonCommercialContainer.style.opacity = "0";
        loadingElement.appendChild(nonCommercialContainer);

        // Use the runtime license check
        if (!isDevEnvironment() && runtimeLicenseCheckPromise) {
            if (debugLicense) console.log("Waiting for runtime license check");
            await runtimeLicenseCheckPromise;
            commercialLicense = hasCommercialLicense();
        }
        if (commercialLicense) return;
        nonCommercialContainer.style.transition = "opacity .5s ease-in-out";
        nonCommercialContainer.style.opacity = "1";
    }
}