// ==========================================
// JavaScript Sandbox — Browser Extension
// ==========================================
// Executes JavaScript in a sandboxed iframe.
// Registers the js_eval tool so the LLM can run code,
// verify calculations, and transform data.
//
// Security: The iframe has sandbox="allow-scripts" only.
// No allow-same-origin, no network, no DOM access.
// ==========================================

Extensions.register({
    id: 'js-sandbox',

    _iframe: null,
    _pendingCallbacks: new Map(), // messageId → { resolve, reject, timer }
    _messageCounter: 0,

    async init(ctx) {
        const self = this;

        // Create the sandboxed iframe on first use (lazy)
        ctx.tools.handle('js_eval', async (args) => {
            return self._execute(args.code || '');
        });
    },

    async _execute(code) {
        if (!code.trim()) {
            return { error: 'No code provided' };
        }

        // Ensure iframe is ready
        await this._ensureIframe();

        const messageId = 'eval-' + (++this._messageCounter);
        const TIMEOUT_MS = 10000;

        return new Promise((resolve) => {
            // Set up timeout
            const timer = setTimeout(() => {
                this._pendingCallbacks.delete(messageId);
                resolve({
                    success: false,
                    error: 'Execution timed out (10s)',
                    output: [],
                    result: null,
                });
            }, TIMEOUT_MS);

            this._pendingCallbacks.set(messageId, { resolve, timer });

            // Send code to sandbox
            this._iframe.contentWindow.postMessage({
                type: 'eval',
                id: messageId,
                code: code,
            }, '*');
        });
    },

    _ensureIframe() {
        if (this._iframe) return Promise.resolve();

        return new Promise((resolve) => {
            // Build sandbox HTML as a blob URL
            const sandboxHTML = this._buildSandboxHTML();
            const blob = new Blob([sandboxHTML], { type: 'text/html' });
            const blobURL = URL.createObjectURL(blob);

            const iframe = document.createElement('iframe');
            iframe.sandbox = 'allow-scripts';
            iframe.style.cssText = 'display:none;width:0;height:0;border:none;position:absolute;';
            iframe.src = blobURL;

            iframe.onload = () => {
                this._iframe = iframe;
                URL.revokeObjectURL(blobURL);
                resolve();
            };

            // Listen for messages from sandbox
            window.addEventListener('message', (event) => {
                // Only accept messages from our sandbox iframe
                if (!this._iframe || event.source !== this._iframe.contentWindow) return;

                const data = event.data;
                if (data?.type !== 'eval-result') return;

                const pending = this._pendingCallbacks.get(data.id);
                if (!pending) return;

                clearTimeout(pending.timer);
                this._pendingCallbacks.delete(data.id);

                pending.resolve({
                    success: !data.error,
                    result: data.result !== undefined ? data.result : null,
                    output: data.output || [],
                    error: data.error || null,
                });
            });

            document.body.appendChild(iframe);
        });
    },

    _buildSandboxHTML() {
        // This HTML runs INSIDE the sandboxed iframe.
        // It has no access to the parent page, no network, no cookies.
        return `<!DOCTYPE html>
<html><head><title>Sandbox</title></head>
<body><script>
(function() {
    'use strict';

    // Listen for eval messages from parent
    window.addEventListener('message', function(event) {
        var msg = event.data;
        if (!msg || msg.type !== 'eval') return;

        var output = [];
        var result = undefined;
        var error = null;

        // Override console methods to capture output
        var origLog = console.log;
        var origWarn = console.warn;
        var origError = console.error;

        console.log = function() {
            var args = Array.prototype.slice.call(arguments);
            output.push({ level: 'log', text: args.map(stringify).join(' ') });
        };
        console.warn = function() {
            var args = Array.prototype.slice.call(arguments);
            output.push({ level: 'warn', text: args.map(stringify).join(' ') });
        };
        console.error = function() {
            var args = Array.prototype.slice.call(arguments);
            output.push({ level: 'error', text: args.map(stringify).join(' ') });
        };

        try {
            // Use indirect eval for global scope
            result = (0, eval)(msg.code);
        } catch (e) {
            error = (e && e.message) ? e.message : String(e);
        }

        // Restore console
        console.log = origLog;
        console.warn = origWarn;
        console.error = origError;

        // Stringify result for safe transfer
        var resultStr;
        try {
            resultStr = stringify(result);
        } catch (e2) {
            resultStr = '[unserializable]';
        }

        // Post result back to parent
        event.source.postMessage({
            type: 'eval-result',
            id: msg.id,
            result: resultStr,
            output: output,
            error: error,
        }, '*');
    });

    function stringify(val) {
        if (val === undefined) return 'undefined';
        if (val === null) return 'null';
        if (typeof val === 'function') return val.toString();
        if (typeof val === 'object') {
            try { return JSON.stringify(val, null, 2); }
            catch(e) { return String(val); }
        }
        return String(val);
    }
})();
</` + `script></body></html>`;
    },

    destroy() {
        // Clean up pending callbacks
        for (const [id, pending] of this._pendingCallbacks) {
            clearTimeout(pending.timer);
            pending.resolve({ success: false, error: 'Extension destroyed', output: [], result: null });
        }
        this._pendingCallbacks.clear();

        // Remove iframe
        if (this._iframe) {
            this._iframe.remove();
            this._iframe = null;
        }
    }
});
