import React, { useEffect, useRef, useState, useCallback } from 'react';
import Editor from '@monaco-editor/react';
import Icon from './Icon';
// Фаза 6.1: декларации game.* API теперь в отдельных файлах engine/types/*.d.ts,
// собираются скриптом _build_bundle.py в bundle.js (16 модулей, ~54КБ).
// Каждый модуль грузим в Monaco как отдельный extraLib — это позволяет
// при правке одного файла не перетряхивать все остальные.
import { GAME_TYPE_LIBS } from './engine/types/bundle';
import { registerSnippets } from './engine/snippets';

/**
 * ScriptEditor — Monaco-редактор кода скрипта в табе.
 *
 * Особенности:
 *   - Дебаунс автосохранения 600мс: пока юзер печатает, не дёргаем onSave.
 *     Через 600мс тишины → onSave(code) → markDirty в KubikonEditor.
 *   - Темная тема (vs-dark).
 *   - Подсветка синтаксических ошибок JS (через настройки jsDefaults).
 *   - Автокомплит на game.* через TypeScript-декларацию (см. setupGameTypes).
 *   - Ctrl+S — мгновенный flush сохранения (без дебаунса).
 *   - Ctrl+/ — комментарий строки (встроенная фича Monaco).
 *
 * Props:
 *   value     — начальный код
 *   onSave(code) — debounced сохранение
 *   onRunSolo(code) — запустить только этот скрипт (sandbox-режим)
 *   isSoloRunning — bool, активен ли solo-режим
 *   scriptId  — для подсветки ошибок (один уникальный URI на скрипт)
 *   target    — null | {kind, ...} — для отображения «привязка к: блок (1,2,3)»
 *   onClose   — закрыть таб (передаётся в шапку)
 */
// Старый одностраничный TYPES_LIB удалён в Фазе 6.1 (2026-05-26).
// Декларации переехали в engine/types/*.d.ts → bundle.js → GAME_TYPE_LIBS (импорт сверху).
// Если нужен какой-то метод, которого нет в автокомплите — добавляйте его
// в соответствующий .d.ts (player.d.ts / scene.d.ts / ...) и пересобирайте
// командой `python _build_bundle.py` в той же папке.
function ScriptEditor({ value, onSave, onRunSolo, isSoloRunning, scriptId, target, onClose, flushRef }) {
    // Локальный буфер кода — то что в редакторе сейчас.
    // Синхронизируется с external value только при смене scriptId.
    const [localCode, setLocalCode] = useState(value || '');
    const prevScriptIdRef = useRef(scriptId);
    const debounceRef = useRef(null);
    const editorRef = useRef(null);
    // Актуальные значения для флаша из родителя (через flushRef).
    // Без них замыкание поймает старый код/scriptId на момент монтирования.
    const localCodeRef = useRef(localCode);
    const onSaveRef = useRef(onSave);
    const scriptIdRef = useRef(scriptId);
    useEffect(() => { localCodeRef.current = localCode; }, [localCode]);
    useEffect(() => { onSaveRef.current = onSave; }, [onSave]);
    useEffect(() => { scriptIdRef.current = scriptId; }, [scriptId]);

    // При смене скрипта — флашим pending-правки предыдущего, потом подгружаем код нового.
    useEffect(() => {
        if (prevScriptIdRef.current !== scriptId) {
            // Если в предыдущем скрипте был активен дебаунс — сохраняем ПОСЛЕДНЮЮ
            // версию кода для прошлого scriptId, иначе правки исчезнут.
            if (debounceRef.current) {
                clearTimeout(debounceRef.current);
                debounceRef.current = null;
                try { onSaveRef.current?.(localCodeRef.current); } catch (_) {}
            }
            setLocalCode(value || '');
            prevScriptIdRef.current = scriptId;
        }
    }, [scriptId, value]);

    // Если прилетело новое value (например после загрузки проекта) — синхронизируем.
    useEffect(() => {
        if (prevScriptIdRef.current === scriptId && value !== localCode) {
            // Синхронизируем только если сильно отличается — иначе перетрут пользовательский ввод
            if (!localCode || (value || '').length > 0 && (value || '') !== localCode && !document.activeElement?.closest?.('.monaco-editor')) {
                setLocalCode(value || '');
            }
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [value]);

    // Дебаунс-сохранение
    const scheduleSave = useCallback((code) => {
        if (debounceRef.current) clearTimeout(debounceRef.current);
        debounceRef.current = setTimeout(() => {
            debounceRef.current = null;
            onSave?.(code);
        }, 600);
    }, [onSave]);

    const flushSave = useCallback(() => {
        if (debounceRef.current) {
            clearTimeout(debounceRef.current);
            debounceRef.current = null;
        }
        // Берём актуальные ссылки из ref — без них Ctrl+S из Monaco
        // мог поймать старое замыкание (binding регистрируется один раз в Mount).
        onSaveRef.current?.(localCodeRef.current);
    }, []);

    // Прокидываем flush в родителя — KubikonEditor вызывает его перед каждым doSave,
    // чтобы pending debounce не пропустил последние правки скрипта.
    useEffect(() => {
        if (!flushRef) return;
        flushRef.current = () => {
            if (debounceRef.current) {
                clearTimeout(debounceRef.current);
                debounceRef.current = null;
                try { onSaveRef.current?.(localCodeRef.current); } catch (_) {}
            }
        };
        return () => { if (flushRef) flushRef.current = null; };
    }, [flushRef]);

    // Очистка дебаунса и ResizeObserver при unmount
    useEffect(() => {
        return () => {
            if (debounceRef.current) {
                clearTimeout(debounceRef.current);
                try { onSaveRef.current?.(localCodeRef.current); } catch (_) {}
            }
            if (editorRef.current?._kubikonRO) {
                try { editorRef.current._kubikonRO.disconnect(); } catch (_) {}
            }
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    const handleChange = (val) => {
        const code = val ?? '';
        setLocalCode(code);
        scheduleSave(code);
    };

    /**
     * Регистрируем декларации game.* в JS-режиме Monaco при mount.
     * Вызывается ОДИН раз для всего приложения (если Monaco загружен повторно — повторно).
     */
    const handleEditorWillMount = (monaco) => {
        try {
            // Подсветка синтаксических ошибок — да, но семантическая (полный TS-анализ)
            // отключена: она тяжёлая и спавнит воркер для каждого файла.
            // На скрипте 50-200 строк это даёт заметный лаг при каждом нажатии.
            monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
                noSemanticValidation: true,
                noSyntaxValidation: false,
                noSuggestionDiagnostics: true,
            });
            monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
                target: monaco.languages.typescript.ScriptTarget.ES2020,
                allowNonTsExtensions: true,
                strict: false,
                noLib: true,
                lib: ['es2020'],
            });
            // Регистрируем 16 typedef-модулей game.* (Фаза 6.1).
            // Каждый файл — отдельный extraLib, чтобы при правке одного
            // не пересоздавать всю кучу. existing проверяем по uri.
            const existing = monaco.languages.typescript.javascriptDefaults.getExtraLibs();
            for (const lib of GAME_TYPE_LIBS) {
                if (!existing[lib.uri]) {
                    monaco.languages.typescript.javascriptDefaults.addExtraLib(lib.content, lib.uri);
                }
            }
            // Сниппеты для быстрого старта (door/coin/portal/npc/quest/save и т.д.).
            // Регистрируются один раз для всего Monaco, флаг хранится в monaco.__kubikonSnippetsRegistered.
            registerSnippets(monaco);
        } catch (e) {
            // eslint-disable-next-line no-console
            console.warn('[ScriptEditor] Monaco setup error', e);
        }
    };

    const handleEditorMount = (editor, monaco) => {
        editorRef.current = editor;
        // Ctrl+S — flush сохранения
        editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
            flushSave();
        });
        // Ctrl+Shift+I — вставить сниппет (вызов автокомплита).
        // Ctrl+Shift+P уже занят встроенной палитрой команд Monaco (это и есть 6.1.5).
        editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyI, () => {
            editor.trigger('keyboard', 'editor.action.triggerSuggest', {});
        });
        // F8 — «Проверить скрипт» (включить полный TS-анализ на 4 сек,
        // потом отключить чтобы не было лагов при печати). Все ошибки
        // подсветятся squiggly-линиями.
        editor.addCommand(monaco.KeyCode.F8, () => {
            try {
                monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
                    noSemanticValidation: false,
                    noSyntaxValidation: false,
                    noSuggestionDiagnostics: false,
                });
                setTimeout(() => {
                    try {
                        monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
                            noSemanticValidation: true,
                            noSyntaxValidation: false,
                            noSuggestionDiagnostics: true,
                        });
                    } catch (_) {}
                }, 4000);
            } catch (_) {}
        });
        // Ручной resize вместо automaticLayout (он внутри Monaco ставит RAF-цикл,
        // что заметно тормозит на больших проектах). Слушаем ResizeObserver
        // на ближайшем контейнере и вызываем editor.layout() только при изменении.
        try {
            const node = editor.getDomNode?.()?.parentElement;
            if (node && typeof ResizeObserver !== 'undefined') {
                let lastW = 0, lastH = 0;
                const ro = new ResizeObserver((entries) => {
                    const e = entries[0];
                    if (!e) return;
                    const w = Math.round(e.contentRect.width);
                    const h = Math.round(e.contentRect.height);
                    if (w === lastW && h === lastH) return;
                    lastW = w; lastH = h;
                    try { editor.layout({ width: w, height: h }); } catch (_) {}
                });
                ro.observe(node);
                editor._kubikonRO = ro;
            }
        } catch (_) { /* ignore */ }
    };

    // Подпись привязки скрипта
    const targetLabel = (() => {
        if (!target) return 'Глобальный скрипт';
        if (target.kind === 'block') {
            const r = target.ref || target;
            return `Привязан к блоку (${r.x}, ${r.y}, ${r.z})`;
        }
        const id = target.id ?? target.ref;
        if (target.kind === 'model') return `Привязан к модели #${id}`;
        if (target.kind === 'primitive') return `Привязан к примитиву #${id}`;
        return 'Привязан к объекту';
    })();

    return (
        <div style={{
            position: 'absolute',
            inset: 0,
            background: '#1e1e1e',
            display: 'flex',
            flexDirection: 'column',
            zIndex: 5,
            fontFamily: '"Roboto Condensed", system-ui, sans-serif',
        }}>
            {/* Шапка таба */}
            <div style={{
                padding: '10px 16px',
                borderBottom: '1px solid #3a3a3a',
                background: 'linear-gradient(180deg, #2e2e2e 0%, #252525 100%)',
                color: '#e8e8ea',
                fontSize: 13,
                display: 'flex',
                alignItems: 'center',
                gap: 12,
                fontFamily: 'inherit',
            }}>
                <div style={{
                    width: 28, height: 28, borderRadius: 8,
                    background: 'linear-gradient(135deg, #4f74ff 0%, #3a57d8 100%)',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    fontSize: 14,
                    color: '#fff',
                    boxShadow: '0 4px 10px rgba(79, 116, 255, 0.45)',
                }}><Icon name="script" size={14} /></div>
                <span style={{
                    fontWeight: 800, color: '#e8e8ea',
                    letterSpacing: -0.2,
                }}>
                    {scriptId === 'demo' ? 'Демо-скрипт' : scriptId}
                </span>
                {targetLabel && (
                    <span style={{
                        background: 'rgba(79, 116, 255, 0.18)',
                        color: '#6d8aff',
                        padding: '3px 10px',
                        borderRadius: 999,
                        fontSize: 11,
                        fontWeight: 700,
                        border: '1px solid rgba(79, 116, 255, 0.35)',
                    }}>{targetLabel}</span>
                )}
                <span style={{ marginLeft: 'auto', display: 'flex', gap: 10, alignItems: 'center' }}>
                    {/* Фаза 6.1.4: кнопка «Проверить» — включает семантический анализ TS
                        на 4 секунды, подсвечивает все ошибки (опечатки в game.X.Y и пр.).
                        F8 — горячая клавиша. */}
                    <button
                        onClick={() => {
                            try {
                                const ed = editorRef.current;
                                if (!ed) return;
                                ed.trigger('keyboard', 'editor.action.marker.next', {});
                                ed.focus();
                                // Сам анализ запускается через F8 — здесь только триггер той же команды.
                                const monaco = window.monaco;
                                if (monaco && monaco.languages?.typescript?.javascriptDefaults) {
                                    monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
                                        noSemanticValidation: false,
                                        noSyntaxValidation: false,
                                        noSuggestionDiagnostics: false,
                                    });
                                    setTimeout(() => {
                                        try {
                                            monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
                                                noSemanticValidation: true,
                                                noSyntaxValidation: false,
                                                noSuggestionDiagnostics: true,
                                            });
                                        } catch (_) {}
                                    }, 4000);
                                }
                            } catch (_) {}
                        }}
                        title="Проверить скрипт на опечатки и несуществующие методы game.* (F8)"
                        style={{
                            display: 'inline-flex',
                            alignItems: 'center',
                            gap: 6,
                            padding: '6px 12px',
                            borderRadius: 8,
                            border: '1px solid rgba(255, 209, 102, 0.45)',
                            background: 'linear-gradient(135deg, rgba(255,209,102,0.18) 0%, rgba(255,158,71,0.18) 100%)',
                            color: '#ffd166',
                            fontSize: 12,
                            fontWeight: 700,
                            cursor: 'pointer',
                            fontFamily: 'inherit',
                            letterSpacing: 0.2,
                        }}
                        onMouseEnter={(e) => {
                            e.currentTarget.style.filter = 'brightness(1.15)';
                        }}
                        onMouseLeave={(e) => {
                            e.currentTarget.style.filter = 'brightness(1)';
                        }}
                    >
                        <span style={{ display: 'inline-flex' }}><Icon name="warning" size={13} /></span>
                        <span>Проверить</span>
                    </button>
                    <button
                        onClick={() => onRunSolo?.(localCode)}
                        title={isSoloRunning ? 'Остановить отладочный запуск' : 'Запустить только этот скрипт (для отладки)'}
                        style={{
                            display: 'inline-flex',
                            alignItems: 'center',
                            gap: 6,
                            padding: '6px 14px',
                            borderRadius: 8,
                            border: '1px solid transparent',
                            background: isSoloRunning
                                ? 'linear-gradient(135deg, #ff6f7a 0%, #c0303f 100%)'
                                : 'linear-gradient(135deg, #22d97a 0%, #0f9d56 100%)',
                            color: '#fff',
                            fontSize: 12,
                            fontWeight: 800,
                            cursor: 'pointer',
                            transition: 'all 200ms cubic-bezier(0.34, 1.56, 0.64, 1)',
                            fontFamily: 'inherit',
                            boxShadow: isSoloRunning
                                ? '0 6px 16px rgba(239, 68, 68, 0.35)'
                                : '0 6px 16px rgba(34, 217, 122, 0.35)',
                            letterSpacing: 0.2,
                        }}
                        onMouseEnter={(e) => {
                            e.currentTarget.style.transform = 'translateY(-1px)';
                            e.currentTarget.style.filter = 'brightness(1.08)';
                        }}
                        onMouseLeave={(e) => {
                            e.currentTarget.style.transform = 'translateY(0)';
                            e.currentTarget.style.filter = 'brightness(1)';
                        }}
                    >
                        <span style={{ display: 'inline-flex' }}><Icon name={isSoloRunning ? 'stop' : 'play'} size={13} /></span>
                        <span>{isSoloRunning ? 'Стоп отладки' : 'Запустить только этот'}</span>
                    </button>
                    <span style={{
                        display: 'inline-flex', alignItems: 'center', gap: 4,
                        color: '#9a9a9e', fontSize: 11, fontWeight: 700,
                    }}>
                        <kbd style={kbdStyle}>Ctrl</kbd>
                        <span>+</span>
                        <kbd style={kbdStyle}>S</kbd>
                        <span style={{ margin: '0 4px' }}>·</span>
                        <kbd style={kbdStyle}>Ctrl</kbd>
                        <span>+</span>
                        <kbd style={kbdStyle}>/</kbd>
                    </span>
                </span>
            </div>

            {/* Редактор */}
            <div style={{ flex: 1, minHeight: 0 }}>
                <Editor
                    height="100%"
                    defaultLanguage="javascript"
                    theme="vs-dark"
                    value={localCode}
                    path={`script_${scriptId}.js`}
                    onChange={handleChange}
                    beforeMount={handleEditorWillMount}
                    onMount={handleEditorMount}
                    options={{
                        fontSize: 14,
                        minimap: { enabled: false },
                        scrollBeyondLastLine: false,
                        wordWrap: 'on',
                        // Вместо automaticLayout (rAF-цикл внутри Monaco — тормозит)
                        // используем явный ResizeObserver в onMount.
                        automaticLayout: false,
                        tabSize: 4,
                        insertSpaces: true,
                        // smoothScrolling и cursorBlinking тоже добавляют rAF-loop'ы
                        smoothScrolling: false,
                        cursorBlinking: 'blink',
                        roundedSelection: true,
                        padding: { top: 12, bottom: 12 },
                        guides: { indentation: true },
                        // bracketPairColorization — дорогая фича, на больших файлах лагает
                        bracketPairColorization: { enabled: false },
                        suggest: { showWords: false },
                        // Скрипты обычно небольшие — render не на каждый кадр
                        renderLineHighlight: 'line',
                        renderWhitespace: 'none',
                        // Mouse wheel: гарантируем что скролл попадает в Monaco
                        mouseWheelScrollSensitivity: 1,
                        scrollbar: {
                            useShadows: false,
                            verticalScrollbarSize: 10,
                            horizontalScrollbarSize: 10,
                            alwaysConsumeMouseWheel: true,
                        },
                    }}
                />
            </div>
        </div>
    );
}

const kbdStyle = {
    background: '#1b1b1b',
    border: '1px solid #4a4a4a',
    borderRadius: 4,
    padding: '1px 6px',
    fontSize: 10,
    fontFamily: 'inherit',
    fontWeight: 800,
    color: '#9a9a9e',
    minWidth: 14,
    textAlign: 'center',
    display: 'inline-block',
    boxShadow: '0 1px 0 rgba(0, 0, 0, 0.3)',
};

export default ScriptEditor;
