// Import PlayCanvas Engine
import * as pc from './playcanvas-engine.mjs';

// Export for global access - create a mutable wrapper object
// ESM namespace objects are frozen, so we spread into a new object
window.pc = { ...pc };

// Configuration Constants
const ASSET_PREFIX = "{{ASSET_PREFIX}}";
const SCRIPT_PREFIX = "{{SCRIPT_PREFIX}}";
const SCENE_PATH = "{{SCENE_PATH}}";
const CONTEXT_OPTIONS = {{CONTEXT_OPTIONS}};
const SCRIPTS = {{SCRIPTS}};
const CONFIG_FILENAME = "config.json";
const INPUT_SETTINGS = {{INPUT_SETTINGS}};
const PRELOAD_MODULES = {{PRELOAD_MODULES}};
if (pc.script) {
    pc.script.legacy = {{LEGACY_SCRIPTS}};
}

// Export configuration constants to window for compatibility with __start__.js
// This is needed when ESM code is bundled as IIFE for playable ads
window.ASSET_PREFIX = ASSET_PREFIX;
window.SCRIPT_PREFIX = SCRIPT_PREFIX;
window.SCENE_PATH = SCENE_PATH;
window.CONTEXT_OPTIONS = CONTEXT_OPTIONS;
window.SCRIPTS = SCRIPTS;
window.INPUT_SETTINGS = INPUT_SETTINGS;
window.PRELOAD_MODULES = PRELOAD_MODULES;

// Script imports will be loaded after app initialization
const SCRIPT_IMPORT_PATHS = {{SCRIPT_IMPORT_PATHS}};

// GameRule path from import map (resolved at build time for bundling)
const GAME_RULE_PATH = "{{GAME_RULE_PATH}}";

// WASM Module Loader
const loadModules = function(modules, urlPrefix) {
    return new Promise(resolve => {
        if (typeof modules === 'undefined' || modules.length === 0) {
            resolve();
        } else {
            let remaining = modules.length;
            const moduleLoaded = () => {
                if (--remaining === 0) {
                    resolve();
                }
            };
            modules.forEach(function(m) {
                pc.WasmModule.setConfig(m.moduleName, {
                    glueUrl: urlPrefix + m.glueUrl,
                    wasmUrl: urlPrefix + m.wasmUrl,
                    fallbackUrl: urlPrefix + m.fallbackUrl
                });
                if (!m.hasOwnProperty('preload') || m.preload) {
                    if (m.moduleName === 'BASIS') {
                        pc.basisInitialize();
                        moduleLoaded();
                    } else if (m.moduleName === 'DracoDecoderModule') {
                        if (pc.dracoInitialize) {
                            pc.dracoInitialize();
                            moduleLoaded();
                        } else {
                            pc.WasmModule.getInstance(m.moduleName, moduleLoaded);
                        }
                    } else {
                        pc.WasmModule.getInstance(m.moduleName, moduleLoaded);
                    }
                } else {
                    moduleLoaded();
                }
            });
        }
    });
};

// Store loaded modules globally for later GameRule initialization
let loadedScriptModules = new Map();
let scriptBaseUrl = '';

// Dynamic script loader - loads scripts after app is initialized
async function loadUserScripts(scriptPaths) {
    if (!scriptPaths || scriptPaths.length === 0) {
        return;
    }
    console.log(`[ESM] Loading ${scriptPaths.length} user scripts...`);
    
    // Get the base URL of the current module
    scriptBaseUrl = new URL('.', import.meta.url).href;
    console.log(`[ESM] Base URL: ${scriptBaseUrl}`);
    
    for (const scriptPath of scriptPaths) {
        try {
            // Resolve the script path relative to this module's location
            const absoluteUrl = new URL(scriptPath, scriptBaseUrl).href;
            console.log(`[ESM] Loading: ${scriptPath} -> ${absoluteUrl}`);
            const module = await import(absoluteUrl);
            loadedScriptModules.set(scriptPath, module);
            console.log(`[ESM] Loaded: ${scriptPath}`);
        } catch (e) {
            console.error(`[ESM] Failed to load script: ${scriptPath}`, e);
        }
    }
    
    // NOTE: Do NOT call initializeGameRule here!
    // GameRule initialization must happen AFTER app.configure() completes,
    // because app.root is not available until then.
    // initializeGameRule is called in configure() after loadScene().
    console.log('[ESM] Scripts loaded. GameRule will be initialized after scene loads.');
}

// Auto-initialize GameRule from loaded modules
// IMPORTANT: This must be called AFTER app.root is available (after loadScene)
async function initializeGameRule() {
    try {
        // Find GameplaySystem module
        let GameplaySystem = null;
        for (const [path, module] of loadedScriptModules) {
            if (module.GameplaySystem) {
                GameplaySystem = module.GameplaySystem;
                console.log(`[ESM] Found GameplaySystem in: ${path}`);
                break;
            }
        }
        
        if (!GameplaySystem) {
            console.log('[ESM] GameplaySystem not found in loaded modules');
            return;
        }
        
        // Check if gameRule is already set
        if (GameplaySystem.gameRule) {
            console.log('[ESM] GameplaySystem.gameRule already initialized');
            return;
        }
        
        // Try to load GameRule using resolved path (not bare specifier for bundling compatibility)
        // The path is resolved from import map at build time
        if (!GAME_RULE_PATH) {
            console.log('[ESM] No gameRule path configured');
            return;
        }
        
        try {
            // Use relative path for dynamic import (bundler-friendly)
            const absoluteUrl = new URL(GAME_RULE_PATH, scriptBaseUrl).href;
            console.log(`[ESM] Loading GameRule from: ${absoluteUrl}`);
            const gameRuleModule = await import(absoluteUrl);
            
            if (gameRuleModule.GameRule) {
                GameplaySystem.gameRule = new gameRuleModule.GameRule();
                console.log(`[ESM] Auto-initialized GameplaySystem.gameRule: ${GameplaySystem.gameRule.ruleName || 'GameRule'}`);
            }
        } catch (e) {
            // GameRule module not found or doesn't export GameRule class
            console.log('[ESM] Could not auto-initialize GameRule:', e.message);
        }
    } catch (e) {
        console.error('[ESM] Error in initializeGameRule:', e);
    }
}

// Canvas Setup
const CANVAS_ID = 'application-canvas';

const getIosVersion = () => {
    if (/iP(hone|od|ad)/.test(navigator.platform)) {
        const v = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);
        return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)];
    }
    return null;
};

let lastWindowHeight = window.innerHeight;
let lastWindowWidth = window.innerWidth;
let windowSizeChangeIntervalHandler = null;

const pcBootstrap = {
    reflowHandler: null,
    iosVersion: getIosVersion(),
    createCanvas: function() {
        const canvas = document.createElement('canvas');
        canvas.setAttribute('id', CANVAS_ID);
        canvas.setAttribute('tabindex', 0);
        canvas.onselectstart = function() { return false; };
        canvas.style['-webkit-user-select'] = 'none';
        document.body.appendChild(canvas);
        return canvas;
    },
    resizeCanvas: function(app, canvas) {
        canvas.style.width = '';
        canvas.style.height = '';
        app.resizeCanvas(canvas.width, canvas.height);
        const fillMode = app._fillMode;
        if (fillMode === pc.FILLMODE_NONE || fillMode === pc.FILLMODE_KEEP_ASPECT) {
            if ((fillMode === pc.FILLMODE_NONE && canvas.clientHeight < window.innerHeight) ||
                canvas.clientWidth / canvas.clientHeight >= window.innerWidth / window.innerHeight) {
                canvas.style.marginTop = Math.floor((window.innerHeight - canvas.clientHeight) / 2) + 'px';
            } else {
                canvas.style.marginTop = '';
            }
        }
        lastWindowHeight = window.innerHeight;
        lastWindowWidth = window.innerWidth;
        if (this.iosVersion && this.iosVersion[0] <= 12) {
            window.scrollTo(0, 0);
        }
    },
    reflow: function(app, canvas) {
        this.resizeCanvas(app, canvas);
        if (windowSizeChangeIntervalHandler === null) {
            windowSizeChangeIntervalHandler = setInterval(() => {
                if (lastWindowHeight !== window.innerHeight || lastWindowWidth !== window.innerWidth) {
                    this.resizeCanvas(app, canvas);
                }
            }, 100);
            setTimeout(function() {
                if (!!windowSizeChangeIntervalHandler) {
                    clearInterval(windowSizeChangeIntervalHandler);
                    windowSizeChangeIntervalHandler = null;
                }
            }, 2000);
        }
    }
};

window.pcBootstrap = pcBootstrap;

const canvas = pcBootstrap.createCanvas();
const app = new pc.AppBase(canvas);

// Make app accessible via pc.app for scripts that use it
// Note: We use window.pc (the original mutable object) instead of the imported pc module
// because ES module namespace objects are frozen and cannot have properties added
if (window.pc) {
    window.pc.app = app;
    console.log('[ESM] window.pc.app has been set:', !!window.pc.app);
} else {
    console.error('[ESM] window.pc is not available!');
}

function initCSS() {
    if (document.head.querySelector) {
        var css = `@media screen and (min-aspect-ratio: ${app._width}/${app._height}) {
            #application-canvas.fill-mode-KEEP_ASPECT {
                width: auto;
                height: 100%;
                margin: 0 auto;
            }
        }`;
        document.head.querySelector('style').innerHTML += css;
    }
    if (canvas.classList) {
        canvas.classList.add(`fill-mode-${app.fillMode}`);
    }
}

function displayError(html) {
    const div = document.createElement('div');
    div.innerHTML = `<table style="background-color: #8CE; width: 100%; height: 100%;">
    <tr>
        <td align="center">
            <div style="display: table-cell; vertical-align: middle;">
                <div style="">${html}</div>
            </div>
        </td>
    </tr>
</table>`;
    document.body.appendChild(div);
}

function createLocalGraphicsDevice() {
    const deviceOptions = CONTEXT_OPTIONS ?? {};
    var LEGACY_WEBGL = 'webgl';
    // 确保 deviceTypes 存在，如果不存在则使用默认值
    var deviceTypes = deviceOptions.deviceTypes 
        ? [...deviceOptions.deviceTypes, LEGACY_WEBGL]
        : ['webgl2', 'webgl1', LEGACY_WEBGL];
    const gpuLibPath = '';
    const gfxOptions = {
        deviceTypes: deviceTypes,
        glslangUrl: gpuLibPath + 'glslang.js',
        twgslUrl: gpuLibPath + 'twgsl.js',
        powerPreference: deviceOptions.powerPreference,
        antialias: deviceOptions.antialias !== false,
        alpha: deviceOptions.alpha === true,
        preserveDrawingBuffer: !!deviceOptions.preserveDrawingBuffer
    };
    return pc.createGraphicsDevice(canvas, gfxOptions);
}

function initApp(device) {
    try {
        var createOptions = new pc.AppOptions();
        createOptions.graphicsDevice = device;
        createOptions.componentSystems = [
            pc.RigidBodyComponentSystem, pc.CollisionComponentSystem, pc.JointComponentSystem,
            pc.AnimationComponentSystem, pc.AnimComponentSystem, pc.ModelComponentSystem,
            pc.RenderComponentSystem, pc.CameraComponentSystem, pc.LightComponentSystem,
            pc.ScriptComponentSystem, pc.SoundComponentSystem, pc.AudioListenerComponentSystem,
            pc.ParticleSystemComponentSystem, pc.ScreenComponentSystem, pc.ElementComponentSystem,
            pc.ButtonComponentSystem, pc.ScrollViewComponentSystem, pc.ScrollbarComponentSystem,
            pc.SpriteComponentSystem, pc.LayoutGroupComponentSystem, pc.LayoutChildComponentSystem,
            pc.ZoneComponentSystem, pc.GSplatComponentSystem
        ];
        createOptions.resourceHandlers = [
            pc.RenderHandler, pc.AnimationHandler, pc.AnimClipHandler, pc.AnimStateGraphHandler,
            pc.ModelHandler, pc.MaterialHandler, pc.TextureHandler, pc.TextHandler,
            pc.JsonHandler, pc.AudioHandler, pc.ScriptHandler, pc.SceneHandler,
            pc.CubemapHandler, pc.HtmlHandler, pc.CssHandler, pc.ShaderHandler,
            pc.HierarchyHandler, pc.FolderHandler, pc.FontHandler, pc.BinaryHandler,
            pc.TextureAtlasHandler, pc.SpriteHandler, pc.TemplateHandler, pc.ContainerHandler,
            pc.GSplatHandler
        ];
        createOptions.elementInput = new pc.ElementInput(canvas, {
            useMouse: INPUT_SETTINGS.useMouse,
            useTouch: INPUT_SETTINGS.useTouch
        });
        createOptions.keyboard = INPUT_SETTINGS.useKeyboard ? new pc.Keyboard(window) : null;
        createOptions.mouse = INPUT_SETTINGS.useMouse ? new pc.Mouse(canvas) : null;
        createOptions.gamepads = INPUT_SETTINGS.useGamepads ? new pc.GamePads() : null;
        createOptions.touch = INPUT_SETTINGS.useTouch && pc.platform.touch ? new pc.TouchDevice(canvas) : null;
        createOptions.assetPrefix = ASSET_PREFIX ?? '';
        createOptions.scriptPrefix = SCRIPT_PREFIX ?? '';
        createOptions.scriptsOrder = SCRIPTS ?? [];
        createOptions.soundManager = new pc.SoundManager();
        createOptions.lightmapper = pc.Lightmapper;
        createOptions.batchManager = pc.BatchManager;
        createOptions.xr = pc.XrManager;
        app.init(createOptions);
        
        // Execute deferred ESM Script registrations (if any)
        // This happens after app.init() so pc.registerScript can work properly
        if (window.__deferredESMScripts) {
            console.log('[ESM] Executing ' + window.__deferredESMScripts.length + ' deferred ESM script registrations...');
            window.__deferredESMScripts.forEach(function(fn) {
                try {
                    fn();
                } catch (e) {
                    console.error('[ESM] Deferred script registration error:', e);
                }
            });
            window.__deferredESMScripts = null;
        }
        
        return true;
    } catch (e) {
        displayError('Could not initialize application. Error: ' + e);
        console.error(e);
        return false;
    }
}

function configure() {
    app.configure(CONFIG_FILENAME, err => {
        if (err) {
            console.error(err);
            return;
        }
        initCSS();
        setTimeout(() => {
            pcBootstrap.reflow(app, canvas);
            pcBootstrap.reflowHandler = function() {
                pcBootstrap.reflow(app, canvas);
            };
            window.addEventListener('resize', pcBootstrap.reflowHandler, false);
            window.addEventListener('orientationchange', pcBootstrap.reflowHandler, false);
            app.preload(() => {
                app.scenes.loadScene(SCENE_PATH, async err => {
                    if (err) {
                        console.error(err);
                        return;
                    }
                    
                    // Initialize GameRule AFTER scene is loaded (app.root is now available)
                    // This fixes: "Cannot read properties of undefined (reading 'root')"
                    console.log('[ESM] Scene loaded, app.root is now available:', !!app.root);
                    await initializeGameRule();
                    
                    app.start();
                });
            });
        });
    });
}

async function main() {
    try {
        const device = await createLocalGraphicsDevice();
        if (initApp(device)) {
            // Load WASM modules
            await loadModules(PRELOAD_MODULES, ASSET_PREFIX);
            
            // Load user scripts AFTER app.init() - this is critical for createScript to work
            await loadUserScripts(SCRIPT_IMPORT_PATHS);
            
            // Configure and start
            configure();
        }
    } catch (e) {
        console.error('Device creation error:', e);
    }
}

// Loading Screen
pc.script.createLoadingScreen(app => {
    const createCss = () => {
        const css = `
            body {
                background-color: #283538;
            }

            #application-splash-wrapper {
                position: absolute;
                top: 0;
                left: 0;
                height: 100%;
                width: 100%;
                background-color: #283538;
            }

            #application-splash {
                position: absolute;
                top: calc(50% - 28px);
                width: 264px;
                left: calc(50% - 132px);
            }

            #progress-bar-container {
                margin: 20px auto 0 auto;
                height: 2px;
                width: 100%;
                background-color: #1d292c;
            }

            #progress-bar {
                width: 0%;
                height: 100%;
                background-color: #f60;
            }

            @media (max-width: 480px) {
                #application-splash {
                    width: 170px;
                    left: calc(50% - 85px);
                }
            }
        `;
        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
    };

    const showSplash = () => {
        const wrapper = document.createElement('div');
        wrapper.id = 'application-splash-wrapper';
        document.body.appendChild(wrapper);
        const splash = document.createElement('div');
        splash.id = 'application-splash';
        wrapper.appendChild(splash);
        const container = document.createElement('div');
        container.id = 'progress-bar-container';
        splash.appendChild(container);
        const bar = document.createElement('div');
        bar.id = 'progress-bar';
        container.appendChild(bar);
    };

    const setProgress = value => {
        const bar = document.getElementById('progress-bar');
        if (bar) {
            value = Math.min(1, Math.max(0, value));
            bar.style.width = `${value * 100}%`;
        }
    };

    const hideSplash = () => {
        document.getElementById('application-splash-wrapper').remove();
    };

    createCss();
    showSplash();
    app.on('preload:end', () => {
        app.off('preload:progress');
    });
    app.on('preload:progress', setProgress);
    app.on('start', hideSplash);
});

// Start the application
main();
