#include "infinite_canvas/web_surface.h"

#include <emscripten/emscripten.h>
#include <string.h>

EM_JS(void, Canvas_Web_Begin_Frame, (), {
    document.querySelectorAll("[data-canvas-web-surface]").forEach((surface) => {
        surface.dataset.canvasActive = "false";
    });
});

EM_JS(void, Canvas_Web_Create_Or_Update, (
    uint32 entity_id,
    const char *p_url,
    float x,
    float y,
    float width,
    float height,
    float opacity,
    int dark_mode), {
    const canvas = Module.canvas;
    const surfaceId = `canvas-web-surface-${entity_id}`;
    let surface = document.getElementById(surfaceId);
    if (!surface) {
        surface = document.createElement("div");
        surface.id = surfaceId;
        surface.dataset.canvasWebSurface = "true";
        surface.style.position = "fixed";
        surface.style.zIndex = "4";
        surface.style.overflow = "hidden";
        surface.style.borderRadius = "8px";
        surface.style.background = dark_mode ? "#0f0f11" : "#fff";

        const frame = document.createElement("iframe");
        frame.title = `Canvas web content ${entity_id}`;
        frame.referrerPolicy = "strict-origin-when-cross-origin";
        frame.sandbox = "allow-downloads allow-forms allow-modals allow-popups allow-same-origin allow-scripts";
        frame.style.width = "100%";
        frame.style.height = "100%";
        frame.style.border = "0";
        frame.style.display = "block";
        surface.appendChild(frame);
        document.body.appendChild(surface);
    }

    surface.dataset.canvasActive = "true";
    const frame = surface.firstElementChild;
    const colorScheme = dark_mode ? "dark" : "light";
    surface.style.colorScheme = colorScheme;
    surface.style.background = dark_mode ? "#0f0f11" : "#fff";
    frame.style.colorScheme = colorScheme;
    const url = UTF8ToString(p_url);
    if (frame.dataset.url !== url) {
        frame.dataset.url = url;
        frame.src = url;
    }

    const canvasBounds = canvas.getBoundingClientRect();
    const scaleX = canvasBounds.width / Math.max(1, canvas.width);
    const scaleY = canvasBounds.height / Math.max(1, canvas.height);
    surface.style.left = `${canvasBounds.left + x * scaleX}px`;
    surface.style.top = `${canvasBounds.top + y * scaleY}px`;
    surface.style.width = `${Math.max(1, width * scaleX)}px`;
    surface.style.height = `${Math.max(1, height * scaleY)}px`;
    surface.style.opacity = `${opacity}`;
    surface.style.pointerEvents = opacity >= 0.99 ? "auto" : "none";
});

EM_JS(void, Canvas_Web_History_Go, (uint32 entity_id, int direction), {
    const surface = document.getElementById(
        `canvas-web-surface-${entity_id}`);
    const frame = surface && surface.firstElementChild;
    if (!frame || !frame.contentWindow) return;
    try {
        frame.contentWindow.history.go(direction);
    } catch (error) {
        console.warn("Canvas iframe history navigation is unavailable", error);
    }
});

EM_JS(int, Canvas_Web_Has_Surface, (uint32 entity_id), {
    return document.getElementById(
        `canvas-web-surface-${entity_id}`) ? 1 : 0;
});

EM_JS(void, Canvas_Image_Create_Or_Update, (
    uint32 entity_id,
    const char *p_source,
    float x,
    float y,
    float width,
    float height,
    float opacity), {
    const canvas = Module.canvas;
    const surfaceId = `canvas-web-surface-${entity_id}`;
    let surface = document.getElementById(surfaceId);
    if (!surface) {
        surface = document.createElement("div");
        surface.id = surfaceId;
        surface.dataset.canvasWebSurface = "true";
        surface.style.position = "fixed";
        surface.style.zIndex = "4";
        surface.style.overflow = "hidden";
        surface.style.borderRadius = "8px";
        surface.style.pointerEvents = "none";

        const image = document.createElement("img");
        image.alt = "Canvas image";
        image.style.width = "100%";
        image.style.height = "100%";
        image.style.objectFit = "contain";
        image.style.display = "block";
        surface.appendChild(image);
        document.body.appendChild(surface);
    }

    surface.dataset.canvasActive = "true";
    const image = surface.firstElementChild;
    const source = UTF8ToString(p_source);
    if (image.dataset.url !== source) {
        if (image.dataset.blobUrl) URL.revokeObjectURL(image.dataset.blobUrl);
        image.dataset.url = source;
        delete image.dataset.blobUrl;
        if (/^(https?:|data:|blob:)/i.test(source)) {
            image.src = source;
        } else {
            try {
                const bytes = FS.readFile(source);
                const extension = source.split(".").pop().toLowerCase();
                const mime = extension === "svg" ? "image/svg+xml" :
                    extension === "png" ? "image/png" :
                    extension === "webp" ? "image/webp" :
                    extension === "gif" ? "image/gif" : "image/jpeg";
                const blobUrl = URL.createObjectURL(new Blob([bytes], {type: mime}));
                image.dataset.blobUrl = blobUrl;
                image.src = blobUrl;
            } catch (error) {
                image.src = source;
            }
        }
    }

    const canvasBounds = canvas.getBoundingClientRect();
    const scaleX = canvasBounds.width / Math.max(1, canvas.width);
    const scaleY = canvasBounds.height / Math.max(1, canvas.height);
    surface.style.left = `${canvasBounds.left + x * scaleX}px`;
    surface.style.top = `${canvasBounds.top + y * scaleY}px`;
    surface.style.width = `${Math.max(1, width * scaleX)}px`;
    surface.style.height = `${Math.max(1, height * scaleY)}px`;
    surface.style.opacity = `${opacity}`;
});

EM_JS(void, Canvas_Web_End_Frame, (), {
    document.querySelectorAll("[data-canvas-web-surface]").forEach((surface) => {
        if (surface.dataset.canvasActive !== "true") {
            const content = surface.firstElementChild;
            if (content && content.dataset.blobUrl) {
                URL.revokeObjectURL(content.dataset.blobUrl);
            }
            surface.remove();
        }
    });
});

EM_JS(void, Canvas_Web_Destroy, (), {
    document.querySelectorAll("[data-canvas-web-surface]").forEach((surface) => {
        const content = surface.firstElementChild;
        if (content && content.dataset.blobUrl) {
            URL.revokeObjectURL(content.dataset.blobUrl);
        }
        surface.remove();
    });
});

static Rectangle Canvas_Web_Content_Bounds(
    const Canvas_Camera *p_camera,
    const Canvas_Scene *p_scene,
    const Canvas_Entity *p_entity)
{
    (void)p_scene;
    float frame_inset = CANVAS_WEB_FRAME_INSET / p_camera->zoom;
    float amount = p_entity->web_chrome_amount;
    if (amount < 0.0f) amount = 0.0f;
    if (amount > 1.0f) amount = 1.0f;
    float eased = amount * amount * (3.0f - 2.0f * amount);
    float toolbar_height =
        CANVAS_WEB_TOOLBAR_HEIGHT * eased / p_camera->zoom;
    float resize_handle = CANVAS_WEB_RESIZE_HANDLE / p_camera->zoom;
    Vector2 position = Canvas_Camera_World_To_Screen(
        p_camera,
        (Vector2){
            p_entity->position.x + frame_inset,
            p_entity->position.y + frame_inset + toolbar_height,
        });
    return (Rectangle){
        position.x,
        position.y,
        (p_entity->size.x - frame_inset - resize_handle) * p_camera->zoom,
        (p_entity->size.y - frame_inset - resize_handle - toolbar_height) *
            p_camera->zoom,
    };
}

static boolean Canvas_Web_Is_Visible(
    const Canvas_Camera *p_camera,
    Rectangle bounds)
{
    return p_camera->zoom >= 0.30f &&
        bounds.x + bounds.width > 0.0f &&
        bounds.y + bounds.height > 0.0f &&
        bounds.x < (float)p_camera->viewport_width &&
        bounds.y < (float)p_camera->viewport_height;
}

static float Canvas_Web_Entity_Opacity(const Canvas_Entity *p_entity)
{
    float amount = p_entity->visibility_amount;
    if (amount < 0.0f) amount = 0.0f;
    if (amount > 1.0f) amount = 1.0f;
    return amount * amount * (3.0f - 2.0f * amount);
}

int32 Canvas_Web_Surface_Execute_Subprocess(int argc, char **p_argv)
{
    (void)argc;
    (void)p_argv;
    return -1;
}

boolean Canvas_Web_Surface_Init(
    Canvas_Web_Surface *p_surface,
    Dowa_Arena *p_arena,
    boolean dark_mode)
{
    memset(p_surface, 0, sizeof(*p_surface));
    p_surface->p_arena = p_arena;
    p_surface->dark_mode = dark_mode;
    p_surface->initialized = TRUE;
    return TRUE;
}

void Canvas_Web_Surface_Set_Dark_Mode(
    Canvas_Web_Surface *p_surface,
    boolean dark_mode)
{
    p_surface->dark_mode = dark_mode;
}

boolean Canvas_Web_Surface_Update_Input(
    Canvas_Web_Surface *p_surface,
    const Canvas_Camera *p_camera,
    const Canvas_Scene *p_scene)
{
    (void)p_surface;
    (void)p_camera;
    (void)p_scene;
    return FALSE;
}

void Canvas_Web_Surface_Sync(
    Canvas_Web_Surface *p_surface,
    const Canvas_Camera *p_camera,
    const Canvas_Scene *p_scene)
{
    if (!p_surface->initialized) return;

    Canvas_Web_Begin_Frame();
    uint32 visible_views = 0;
    for (size_t index = 0;
         index < Dowa_Array_Length(p_scene->p_entities) &&
             visible_views < CANVAS_MAX_WEB_VIEWS;
         index++) {
        const Canvas_Entity *p_entity = &p_scene->p_entities[index];
        if (p_entity->type != CANVAS_ENTITY_WEB_CONTENT &&
            p_entity->type != CANVAS_ENTITY_IMAGE) {
            continue;
        }
        Rectangle bounds = Canvas_Web_Content_Bounds(
            p_camera,
            p_scene,
            p_entity);
        if (!Canvas_Web_Is_Visible(p_camera, bounds)) continue;
        float opacity = Canvas_Web_Entity_Opacity(p_entity);
        if (p_entity->type == CANVAS_ENTITY_IMAGE) {
            Canvas_Image_Create_Or_Update(
                p_entity->id,
                p_entity->text,
                bounds.x,
                bounds.y,
                bounds.width,
                bounds.height,
                opacity);
        } else {
            Canvas_Web_Create_Or_Update(
                p_entity->id,
                p_entity->text,
                bounds.x,
                bounds.y,
                bounds.width,
                bounds.height,
                opacity,
                p_surface->dark_mode ? 1 : 0);
        }
        visible_views++;
    }
    Canvas_Web_End_Frame();
}

void Canvas_Web_Surface_Draw(
    const Canvas_Web_Surface *p_surface,
    const Canvas_Camera *p_camera,
    const Canvas_Scene *p_scene)
{
    (void)p_surface;
    (void)p_camera;
    (void)p_scene;
}

void Canvas_Web_Surface_Draw_Entity(
    const Canvas_Web_Surface *p_surface,
    const Canvas_Camera *p_camera,
    const Canvas_Scene *p_scene,
    size_t entity_index)
{
    (void)p_surface;
    (void)p_camera;
    (void)p_scene;
    (void)entity_index;
}

const char *Canvas_Web_Surface_URL(
    const Canvas_Web_Surface *p_surface,
    uint32 entity_id)
{
    (void)p_surface;
    (void)entity_id;
    return NULL;
}

boolean Canvas_Web_Surface_Can_Go_Back(
    const Canvas_Web_Surface *p_surface,
    uint32 entity_id)
{
    (void)p_surface;
    return Canvas_Web_Has_Surface(entity_id) ? TRUE : FALSE;
}

boolean Canvas_Web_Surface_Can_Go_Forward(
    const Canvas_Web_Surface *p_surface,
    uint32 entity_id)
{
    (void)p_surface;
    return Canvas_Web_Has_Surface(entity_id) ? TRUE : FALSE;
}

void Canvas_Web_Surface_Go_Back(
    Canvas_Web_Surface *p_surface,
    uint32 entity_id)
{
    (void)p_surface;
    Canvas_Web_History_Go(entity_id, -1);
}

void Canvas_Web_Surface_Go_Forward(
    Canvas_Web_Surface *p_surface,
    uint32 entity_id)
{
    (void)p_surface;
    Canvas_Web_History_Go(entity_id, 1);
}

boolean Canvas_Web_Surface_Is_Focused(const Canvas_Web_Surface *p_surface)
{
    (void)p_surface;
    return FALSE;
}

void Canvas_Web_Surface_Shutdown(Canvas_Web_Surface *p_surface)
{
    if (!p_surface->initialized) return;
    Canvas_Web_Destroy();
    p_surface->initialized = FALSE;
}
