import { Wall } from "./wall.js";
import { getUserId, newSession, waitForLogin } from "./session.js";
import { debounce } from "./framework.js";
import { ReticleCursor } from "./reticle-renderer.js";

const updateInterval = 1000 / 60;

let main = document.querySelector("main");
let canvasRenderer = main.querySelector("rkgk-canvas-renderer");
let reticleRenderer = main.querySelector("rkgk-reticle-renderer");
let brushEditor = main.querySelector("rkgk-brush-editor");

reticleRenderer.connectViewport(canvasRenderer.viewport);

// In the background, connect to the server.
(async () => {
    await waitForLogin();
    console.info("login ready! starting session");

    let session = await newSession(getUserId(), localStorage.getItem("rkgk.mostRecentWallId"), {
        brush: brushEditor.code,
    });
    localStorage.setItem("rkgk.mostRecentWallId", session.wallId);

    let wall = new Wall(session.wallInfo);
    canvasRenderer.initialize(wall);

    for (let onlineUser of session.wallInfo.online) {
        wall.onlineUsers.addUser(onlineUser.sessionId, {
            nickname: onlineUser.nickname,
            brush: onlineUser.init.brush,
        });
    }

    let currentUser = wall.onlineUsers.getUser(session.sessionId);

    session.addEventListener("error", (event) => console.error(event));

    session.addEventListener("wallEvent", (event) => {
        let wallEvent = event.wallEvent;
        if (wallEvent.sessionId != session.sessionId) {
            if (wallEvent.kind.event == "join") {
                wall.onlineUsers.addUser(wallEvent.sessionId, {
                    nickname: wallEvent.kind.nickname,
                    brush: wallEvent.kind.init.brush,
                });
            }

            let user = wall.onlineUsers.getUser(wallEvent.sessionId);
            if (user == null) {
                console.warn("received event for an unknown user", wallEvent);
                return;
            }

            if (wallEvent.kind.event == "leave") {
                if (user.reticle != null) {
                    reticleRenderer.removeReticle(user.reticle);
                }
                wall.onlineUsers.removeUser(wallEvent.sessionId);
            }

            if (wallEvent.kind.event == "cursor") {
                if (user.reticle == null) {
                    user.reticle = new ReticleCursor(
                        wall.onlineUsers.getUser(wallEvent.sessionId).nickname,
                    );
                    reticleRenderer.addReticle(user.reticle);
                }

                let { x, y } = wallEvent.kind.position;
                user.reticle.setCursor(x, y);
            }

            if (wallEvent.kind.event == "setBrush") {
                user.setBrush(wallEvent.kind.brush);
            }

            if (wallEvent.kind.event == "plot") {
                for (let { x, y } of wallEvent.kind.points) {
                    user.renderBrushToChunks(wall, x, y);
                }
            }
        }
    });

    let sendViewportUpdate = debounce(updateInterval, () => {
        let visibleRect = canvasRenderer.getVisibleChunkRect();
        session.sendViewport(visibleRect);
    });
    canvasRenderer.addEventListener(".viewportUpdate", sendViewportUpdate);
    sendViewportUpdate();

    session.addEventListener("chunks", async (event) => {
        let { chunkInfo, chunkData, hasMore } = event;

        console.info("received data for chunks", {
            chunkInfo,
            chunkDataSize: chunkData.size,
        });

        let updatePromises = [];
        for (let info of event.chunkInfo) {
            if (info.length > 0) {
                let blob = chunkData.slice(info.offset, info.offset + info.length, "image/webp");
                updatePromises.push(
                    createImageBitmap(blob).then((bitmap) => {
                        let chunk = wall.getOrCreateChunk(info.position.x, info.position.y);
                        chunk.ctx.globalCompositeOperation = "copy";
                        chunk.ctx.drawImage(bitmap, 0, 0);
                        chunk.syncToPixmap();
                    }),
                );
            }
        }

        await Promise.all(updatePromises);
        if (hasMore) {
            console.info("more chunks are pending; requesting more");
            session.sendMoreChunks();
        }
    });

    let reportCursor = debounce(updateInterval, (x, y) => session.sendCursor(x, y));
    canvasRenderer.addEventListener(".cursor", async (event) => {
        reportCursor(event.x, event.y);
    });

    let plotQueue = [];
    async function flushPlotQueue() {
        let points = plotQueue.splice(0, plotQueue.length);
        if (points.length != 0) {
            session.sendPlot(points);
        }
    }

    setInterval(flushPlotQueue, updateInterval);

    canvasRenderer.addEventListener(".paint", async (event) => {
        plotQueue.push({ x: event.x, y: event.y });
        currentUser.renderBrushToChunks(wall, event.x, event.y);
    });

    canvasRenderer.addEventListener(".viewportUpdate", () => reticleRenderer.render());

    currentUser.setBrush(brushEditor.code);
    brushEditor.addEventListener(".codeChanged", async () => {
        flushPlotQueue();
        currentUser.setBrush(brushEditor.code);
        session.sendSetBrush(brushEditor.code);
    });

    session.eventLoop();
})();
