export class Reticle extends HTMLElement {
    render(_viewport, _windowSize) {
        throw new Error("Reticle.render must be overridden");
    }
}

export class ReticleCursor extends Reticle {
    #container;

    #lastX = 0;
    #lastY = 0;
    #targetX = 0;
    #targetY = 0;
    #lastUpdate = 0;

    constructor(nickname) {
        super();
        this.nickname = nickname;
    }

    connectedCallback() {
        this.style.setProperty("--color", this.getColor());

        this.#container = this.appendChild(document.createElement("div"));
        this.#container.classList.add("container");

        this.classList.add("cursor");

        let arrow = this.#container.appendChild(document.createElement("div"));
        arrow.classList.add("arrow");

        let nickname = this.#container.appendChild(document.createElement("div"));
        nickname.classList.add("nickname");
        nickname.textContent = this.nickname;
    }

    getColor() {
        let hash = 8803;
        for (let i = 0; i < this.nickname.length; ++i) {
            hash = (hash << 5) - hash + this.nickname.charCodeAt(i);
            hash |= 0;
        }
        return `oklch(65% 0.2 ${(hash / 0x7fffffff) * 360}deg)`;
    }

    setCursor(x, y) {
        this.#lastX = this.#targetX;
        this.#lastY = this.#targetY;
        this.#targetX = x;
        this.#targetY = y;
        this.#lastUpdate = performance.now();
        this.dispatchEvent(new Event(".update"));
    }

    render(viewport, updateInterval, windowSize) {
        let sinceLastUpdate = performance.now() - this.#lastUpdate;
        let t = Math.max(0, Math.min(1, sinceLastUpdate / updateInterval));

        let x = lerp(this.#lastX, this.#targetX, t);
        let y = lerp(this.#lastY, this.#targetY, t);

        let [viewportX, viewportY] = viewport.toScreenSpace(x, y, windowSize);
        this.style.transform = `translate(${viewportX}px, ${viewportY}px)`;

        let needsRerender = t < 1; // if the linear animation isn't yet done
        return needsRerender;
    }
}

customElements.define("rkgk-reticle-cursor", ReticleCursor);

export class ReticleRenderer extends HTMLElement {
    updateInterval = 1000 / 10; // a really wrong value, you should definitely override this

    #reticles = new Set();
    #reticlesDiv;

    #animatingReticles = new Set();

    connectedCallback() {
        this.#reticlesDiv = this.appendChild(document.createElement("div"));
        this.#reticlesDiv.classList.add("reticles");

        this.render();
        let resizeObserver = new ResizeObserver(() => this.render());
        resizeObserver.observe(this);
    }

    connectViewport(viewport) {
        this.viewport = viewport;
        this.render();
    }

    addReticle(reticle) {
        if (!this.#reticles.has(reticle)) {
            reticle.addEventListener(".update", () => {
                let needsKickstart = this.#animatingReticles.size == 0;
                this.#animatingReticles.add(reticle);
                if (needsKickstart) {
                    this.#tickAnimatingReticles();
                }
            });

            this.#reticles.add(reticle);
            this.#reticlesDiv.appendChild(reticle);
        }
    }

    removeReticle(reticle) {
        if (this.#reticles.has(reticle)) {
            this.#reticles.delete(reticle);
            this.#reticlesDiv.removeChild(reticle);
        }
    }

    #tickAnimatingReticles() {
        if (this.viewport == null) return;

        let windowSize = {
            width: this.clientWidth,
            height: this.clientHeight,
        };
        for (let reticle of this.#animatingReticles) {
            let needsUpdate = reticle.render(this.viewport, this.updateInterval, windowSize);
            if (!needsUpdate) {
                this.#animatingReticles.delete(reticle);
            }
        }

        if (this.#animatingReticles.size > 0) {
            requestAnimationFrame(() => this.#tickAnimatingReticles());
        }
    }

    render() {
        if (this.viewport == null) {
            console.debug("viewport is disconnected, skipping transform update");
            return;
        }

        let windowSize = { width: this.clientWidth, height: this.clientHeight };
        for (let reticle of this.#reticles.values()) {
            reticle.render(this.viewport, this.updateInterval, windowSize);
        }
    }
}

customElements.define("rkgk-reticle-renderer", ReticleRenderer);

function lerp(a, b, t) {
    return a + (b - a) * t;
}
