#include "crWasmBridge.h"

#ifdef __EMSCRIPTEN__   // native builds compile this to an empty TU

#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#include <stdio.h>
#include <SDL3/SDL.h>

#include "crApp.h"
#include "crBuildInfo.h"
#include "crDevConsole.h"
#include "crGraphics.h"
#include "crPostProcess.h"
#include "crScreenRef.h"

static const char* CANVAS_HOLDER = "#canvas-holder";   // shell layout authority — measuring the canvas instead feeds SDL's own writes back

static crApp* g_app = nullptr;

static int32_t g_lastScale   = -1;
static int32_t g_lastFps     = -1;
static int32_t g_lastSmaa    = -1;
static int32_t g_lastDevUi   = -1;
static int32_t g_lastConsole = -1;

// SDL logs bypass Module.print and land in the browser console — stdout routes them to the on-page log
static void SDLCALL OnSdlLog( void* userdata, int category, SDL_LogPriority priority, const char* message )
{
    ( void )userdata;
    ( void )category;

    static const char* PRIORITY_NAMES[] = { "INVALID", "TRACE", "VERBOSE", "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" };

    const int32_t index = static_cast<int32_t>( priority );
    const char*   name  = ( ( index >= 0 ) && ( index < static_cast<int32_t>( SDL_arraysize( PRIORITY_NAMES ) ) ) ) ? PRIORITY_NAMES[ index ] : "?";

    printf( "[%s] %s\n", name, message );
}

static void SyncWindowToHolder( SDL_Window* window )
{
    double cssWidth  = 0.0;
    double cssHeight = 0.0;
    const EMSCRIPTEN_RESULT result = emscripten_get_element_css_size( CANVAS_HOLDER, &cssWidth, &cssHeight );

    if( ( cssWidth <= 0.0 ) || ( cssHeight <= 0.0 ) )
    {
        SDL_LogWarn( SDL_LOG_CATEGORY_APPLICATION, "crWasmBridge: holder query failed( result %d, %.1fx%.1f )", result, cssWidth, cssHeight );
        return;
    }

    SDL_SetWindowSize( window, static_cast<int>( cssWidth ), static_cast<int>( cssHeight ) );
}

static EM_BOOL OnBrowserResize( int eventType, const EmscriptenUiEvent* uiEvent, void* userData )
{
    ( void )eventType;
    ( void )uiEvent;

    SyncWindowToHolder( static_cast<SDL_Window*>( userData ) );
    return EM_TRUE;
}

extern "C"
{
    EMSCRIPTEN_KEEPALIVE void crShellSetRenderScale( int32_t percent )
    {
        if( g_app == nullptr )
            return;   // the controls are clickable while the module is still downloading

        g_app->graphics->PostProcess()->SetRenderScale( static_cast<float>( percent ) * 0.01f );
    }
    EMSCRIPTEN_KEEPALIVE void crShellSetTargetFps( int32_t fps )
    {
        if( g_app == nullptr )
            return;

        g_app->targetFps = static_cast<float>( fps );
    }
    EMSCRIPTEN_KEEPALIVE void crShellSetSmaa( int32_t on )
    {
        if( g_app == nullptr )
            return;

        g_app->graphics->PostProcess()->smaa.enabled = ( on != 0 );
    }
    EMSCRIPTEN_KEEPALIVE void crShellToggleDevUi()
    {
        if( g_app == nullptr )
            return;

        g_app->showDevUi = ( g_app->showDevUi == false );
    }
    EMSCRIPTEN_KEEPALIVE void crShellToggleDevConsole()
    {
        if( g_app == nullptr )
            return;

        g_app->devConsole->enabled = ( g_app->devConsole->enabled == false );
        if( g_app->devConsole->enabled )
            g_app->showDevUi = true;   // the panel only renders inside crApp's dev-ui gate
    }
}

EM_JS( void, PushDevUiState, ( int32_t scalePercent, int32_t targetFps, int32_t smaaOn, int32_t devUiOn, int32_t consoleOn ),
{
    if( globalThis.crSyncDevUi )
        globalThis.crSyncDevUi( scalePercent, targetFps, smaaOn, devUiOn, consoleOn );
} );

// writes straight into the wasm heap: console lines are ASCII, and this keeps the shell off
// ccall/cwrap, which this build does not export. returns 0 when the queue is empty
EM_JS( int32_t, PullShellCommand, ( char* out, int32_t capacity ),
{
    if( !globalThis.crConsoleQueue || ( globalThis.crConsoleQueue.length === 0 ) )
        return 0;

    var line  = globalThis.crConsoleQueue.shift();
    var count = 0;
    for( var i = 0; ( i < line.length ) && ( count < ( capacity - 1 ) ); ++i )
    {
        var code = line.charCodeAt( i );
        if( code < 128 )
            HEAPU8[ out + count++ ] = code;
    }
    HEAPU8[ out + count ] = 0;
    return count;
} );

EM_JS( void, PushScreenRef, ( float aspect, int32_t isPortrait ),
{
    if( globalThis.crApplyScreenRef )
        globalThis.crApplyScreenRef( aspect, isPortrait !== 0 );
} );

EM_JS( void, PushFullscreen, ( int32_t enable ),
{
    if( globalThis.crSetFullscreen )
        globalThis.crSetFullscreen( enable !== 0 );
} );

EM_JS( int32_t, QueryFullscreen, ( void ),
{
    return ( document.fullscreenElement !== null ) ? 1 : 0;
} );

EM_JS( void, PushGlContextInfo, ( int32_t antialias, int32_t alpha ),
{
    if( globalThis.crShowGlContext )
        globalThis.crShowGlContext( antialias !== 0, alpha !== 0 );
} );

void crWasmBridge::InstallLogRedirect()
{
    SDL_SetLogOutputFunction( OnSdlLog, nullptr );

    SDL_LogInfo( SDL_LOG_CATEGORY_APPLICATION, "build: %s", crBuildInfo::STAMP );
}
void crWasmBridge::AttachWindow( SDL_Window* window )
{
    // reshape the holder to the build's screen reference BEFORE the first measurement — a style
    // change fires no window resize event, so a late push would leave the canvas mis-sized
    PushScreenRef( crScreenRef::ASPECT, ( crScreenRef::Orientation() == ERefOrientation::PORTRAIT ) ? 1 : 0 );

    emscripten_set_resize_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, window, EM_FALSE, OnBrowserResize );

    SyncWindowToHolder( window );
}
void crWasmBridge::AttachApp( crApp* app )
{
    g_app = app;

    {// what the browser actually handed over — nothing here requests either attribute
        EmscriptenWebGLContextAttributes attrs;
        if( emscripten_webgl_get_context_attributes( emscripten_webgl_get_current_context(), &attrs ) == EMSCRIPTEN_RESULT_SUCCESS )
            PushGlContextInfo( attrs.antialias ? 1 : 0, attrs.alpha ? 1 : 0 );
    }
}
void crWasmBridge::SyncDevUi()
{
    if( g_app == nullptr )
        return;

    const crPostProcess* post = g_app->graphics->PostProcess();

    const int32_t scalePercent = static_cast<int32_t>( ( post->RenderScale() * 100.0f ) + 0.5f );
    const int32_t targetFps    = static_cast<int32_t>( g_app->targetFps );
    const int32_t smaaOn       = post->smaa.enabled ? 1 : 0;
    const int32_t devUiOn      = g_app->showDevUi ? 1 : 0;
    const int32_t consoleOn    = g_app->devConsole->enabled ? 1 : 0;

    if( ( scalePercent == g_lastScale ) && ( targetFps == g_lastFps ) && ( smaaOn == g_lastSmaa )
        && ( devUiOn == g_lastDevUi ) && ( consoleOn == g_lastConsole ) )
        return;

    g_lastScale   = scalePercent;
    g_lastFps     = targetFps;
    g_lastSmaa    = smaaOn;
    g_lastDevUi   = devUiOn;
    g_lastConsole = consoleOn;

    PushDevUiState( scalePercent, targetFps, smaaOn, devUiOn, consoleOn );
}
void crWasmBridge::RequestFullscreen( bool enable )
{
    PushFullscreen( enable ? 1 : 0 );
}
bool crWasmBridge::IsFullscreen()
{
    return ( QueryFullscreen() != 0 );
}
void crWasmBridge::PumpShellConsole()
{
    if( g_app == nullptr )
        return;

    char line[ 256 ];
    while( PullShellCommand( line, static_cast<int32_t>( sizeof( line ) ) ) > 0 )
        g_app->devConsole->Execute( line );
}

#endif
