// ==========================================
// Debug Modal — REPL Tab
// ==========================================
// JavaScript REPL with top-level await, tab-completion,
// command history, and collapsible JSON output.
// Admin-gated OR ?debug=1 URL param.
//
// v0.37.18: Preact rebuild from repl.js (v0.37.14).

const html = window.html;
const { useState, useRef, useEffect, useCallback } = window.hooks;

function _esc(s) {
    if (!s) return '';
    return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

function _getGlobals() {
    const globals = {};
    if (window.sw) globals.sw = window.sw;
    if (window.DebugLog) globals.DebugLog = window.DebugLog;
    globals.$ = (sel) => document.querySelector(sel);
    globals.$$ = (sel) => [...document.querySelectorAll(sel)];
    globals.sleep = (ms) => new Promise(r => setTimeout(r, ms));
    return globals;
}

function _wrapCode(code) {
    const trimmed = code.trim();
    if (/^(let |const |var |return |if |for |while |switch |try |class |function )/.test(trimmed)) {
        return trimmed;
    }
    return `return (${trimmed})`;
}

function _formatValue(value) {
    if (value === null) return 'null';
    if (value === undefined) return 'undefined';
    const type = typeof value;
    if (type === 'string') return JSON.stringify(value);
    if (type === 'number' || type === 'bigint' || type === 'boolean') return String(value);
    if (type === 'function') return `[Function: ${value.name || 'anonymous'}]`;
    if (type === 'symbol') return String(value);
    if (value instanceof HTMLElement) {
        return `<${value.tagName.toLowerCase()}${value.id ? '#' + value.id : ''}${value.className ? '.' + value.className.split(' ').join('.') : ''}>`;
    }
    if (value instanceof Error) return `${value.constructor.name}: ${value.message}`;
    try { return JSON.stringify(value, null, 2); }
    catch (_) { return String(value); }
}

/**
 * @param {{ visible: boolean }} props
 */
export function ReplTab({ visible }) {
    const [output, setOutput] = useState([]);
    const [historyList] = useState(() => {
        try {
            const saved = sessionStorage.getItem('cs::repl::history');
            return saved ? JSON.parse(saved) : [];
        } catch (_) { return []; }
    });
    const historyIdx = useRef(-1);
    const scratchRef = useRef('');
    const inputRef = useRef(null);
    const outputRef = useRef(null);

    // Admin gate
    const isAdmin = window.sw?.auth?.user?.role === 'admin';
    const debugParam = new URLSearchParams(window.location.search).has('debug');
    if (!isAdmin && !debugParam) return null;

    const appendEntry = useCallback((type, text) => {
        setOutput(prev => [...prev, { type, text }]);
    }, []);

    const runCode = useCallback(async (code) => {
        if (!code.trim()) return;

        historyList.push(code);
        if (historyList.length > 100) historyList.shift();
        historyIdx.current = -1;
        try { sessionStorage.setItem('cs::repl::history', JSON.stringify(historyList)); } catch (_) {}

        appendEntry('input', '> ' + code);

        const startTime = performance.now();
        try {
            const globals = _getGlobals();
            const argNames = Object.keys(globals);
            const argValues = Object.values(globals);
            const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
            const fn = new AsyncFunction(...argNames, _wrapCode(code));
            const result = await fn(...argValues);
            const elapsed = (performance.now() - startTime).toFixed(1);

            if (result !== undefined) {
                appendEntry('result', `${_formatValue(result)} [${elapsed}ms]`);
            } else {
                appendEntry('info', `(undefined) [${elapsed}ms]`);
            }
        } catch (err) {
            appendEntry('error', err.message || String(err));
            if (err.stack) {
                appendEntry('stack', err.stack.split('\n').slice(1, 5).join('\n'));
            }
        }
    }, [historyList, appendEntry]);

    const onKeyDown = useCallback((e) => {
        const input = e.target;

        if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            runCode(input.value);
            input.value = '';
            return;
        }

        if (e.key === 'ArrowUp' && input.value.substring(0, input.selectionStart).indexOf('\n') === -1) {
            e.preventDefault();
            if (historyIdx.current === -1) {
                scratchRef.current = input.value;
                historyIdx.current = historyList.length;
            }
            historyIdx.current = Math.max(0, historyIdx.current - 1);
            input.value = historyList[historyIdx.current] || '';
            input.selectionStart = input.selectionEnd = input.value.length;
            return;
        }
        if (e.key === 'ArrowDown' && input.value.substring(input.selectionStart).indexOf('\n') === -1) {
            e.preventDefault();
            if (historyIdx.current === -1) return;
            historyIdx.current++;
            if (historyIdx.current >= historyList.length) {
                historyIdx.current = -1;
                input.value = scratchRef.current || '';
            } else {
                input.value = historyList[historyIdx.current] || '';
            }
            input.selectionStart = input.selectionEnd = input.value.length;
            return;
        }

        if (e.key === 'Tab') {
            e.preventDefault();
            _tabComplete(input);
        }
    }, [runCode, historyList]);

    useEffect(() => {
        if (outputRef.current) {
            outputRef.current.scrollTop = outputRef.current.scrollHeight;
        }
    }, [output]);

    if (!visible) return null;

    return html`
        <div class="debug-repl-tab">
            <div class="debug-repl-output" ref=${outputRef}>
                ${output.map((entry, i) => html`
                    <div key=${i} class="repl-entry repl-entry-${entry.type}">
                        <pre>${_esc(entry.text)}</pre>
                    </div>`)}
            </div>
            <div class="debug-repl-input">
                <input type="text" ref=${inputRef}
                       placeholder="Type expression\u2026"
                       onKeyDown=${onKeyDown} />
            </div>
        </div>`;
}

function _tabComplete(input) {
    const cursor = input.selectionStart;
    const text = input.value.substring(0, cursor);

    const propMatch = text.match(/([\w$.]+)\.([\w]*)$/);
    if (propMatch) {
        try {
            const globals = _getGlobals();
            const obj = new Function(...Object.keys(globals), `return ${propMatch[1]}`)(...Object.values(globals));
            if (obj == null) return;

            const keys = [];
            for (let o = obj; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) {
                for (const k of Object.getOwnPropertyNames(o)) {
                    if (k.startsWith(propMatch[2]) && !k.startsWith('_')) keys.push(k);
                }
            }
            const unique = [...new Set(keys)].sort();
            if (unique.length === 1) {
                _insertCompletion(input, cursor, propMatch[2], unique[0]);
            } else if (unique.length > 1) {
                const common = _commonPrefix(unique);
                if (common.length > propMatch[2].length) {
                    _insertCompletion(input, cursor, propMatch[2], common);
                }
            }
        } catch (_) {}
        return;
    }

    const wordMatch = text.match(/([\w$]+)$/);
    if (wordMatch) {
        const globals = Object.keys(_getGlobals());
        const candidates = [...globals, 'document', 'window', 'console', 'fetch', 'JSON', 'Math',
                            'Array', 'Object', 'String', 'Number', 'Boolean', 'Promise', 'Map', 'Set'];
        const matches = [...new Set(candidates)].filter(c => c.startsWith(wordMatch[1])).sort();
        if (matches.length === 1) {
            _insertCompletion(input, cursor, wordMatch[1], matches[0]);
        } else if (matches.length > 1) {
            const common = _commonPrefix(matches);
            if (common.length > wordMatch[1].length) {
                _insertCompletion(input, cursor, wordMatch[1], common);
            }
        }
    }
}

function _insertCompletion(input, cursor, partial, completion) {
    const before = input.value.substring(0, cursor - partial.length);
    const after = input.value.substring(cursor);
    input.value = before + completion + after;
    const newPos = before.length + completion.length;
    input.selectionStart = input.selectionEnd = newPos;
}

function _commonPrefix(strs) {
    if (strs.length === 0) return '';
    let prefix = strs[0];
    for (let i = 1; i < strs.length; i++) {
        while (!strs[i].startsWith(prefix)) prefix = prefix.slice(0, -1);
    }
    return prefix;
}
