import React, { useEffect, useRef, useState, useCallback } from 'react';
import cl from './ModelEditorScreen.module.css';
import Icon from './Icon';
import { VoxelModelScene, ALLOWED_GRID_SIZES, VOXEL_TEXTURES } from './engine/VoxelModelScene';
import {
    createUserModel, updateUserModel, getUserModel,
    publishUserModel, unpublishUserModel,
} from '../api/Kubikon3DService';
import ModelSaveModal from './ModelSaveModal';

/** Debounce-пауза перед автосейвом после последнего изменения (мс). */
const AUTOSAVE_DEBOUNCE_MS = 2500;

/**
 * ModelEditorScreen — полноэкранный режим создания пользовательской модели.
 *
 * Этап 4 редактора моделей: воксельный редактор полностью функционален.
 *   - Babylon-сцена с орбит-камерой и сеткой 8/16/32/64
 *   - Инструменты: Рисовать / Стереть / Раскрасить / Заливка (Shift+ЛКМ = инверсия draw)
 *   - Палитра 32 цвета + color-picker
 *   - Сохранение в БД через API из Этапа 1 (createUserModel / updateUserModel)
 *   - Thumbnail снимается автоматически с текущего вида camera
 *
 * Гладкий редактор (mode='smooth') пока placeholder — будет на Этапе 7.
 *
 * Props:
 *   mode    — 'voxel' | 'smooth'
 *   userId  — id текущего пользователя (для сохранения)
 *   onClose — закрыть и вернуться в редактор сцены
 *   editingModelId — если передан, загружаем существующую модель для редактирования
 */

// Базовая палитра 32 цветов — natural + bright spectrum,
// удобная для быстрого выбора (как в MagicaVoxel).
const DEFAULT_PALETTE = [
    // Тёплые
    '#ffffff', '#cccccc', '#888888', '#444444', '#000000', '#7c5430', '#a06a3a', '#553a25',
    // Зелень
    '#52b15a', '#5fa84e', '#3f7a3a', '#1f5a1f', '#a8e060', '#6ab04c',
    // Голубой / морской
    '#3a8fd6', '#4673b8', '#1e4d8c', '#7ec8ff', '#b4e4ff',
    // Тёплые акценты
    '#d4c84a', '#e6d27a', '#f7c95c', '#ff9933', '#cc6633',
    // Красное
    '#b84141', '#d44a4a', '#7a1f1f', '#ff5577',
    // Фиолет/розовый
    '#9b5de5', '#c084fc', '#ff66cc',
    // Снег/лёд
    '#e0f0ff',
];

const TOOLS = [
    { id: 'draw',  label: 'Рисовать',   icon: 'paintbrush', desc: 'Поставить voxel выбранного цвета (Shift+ЛКМ = стереть)' },
    { id: 'erase', label: 'Стереть',    icon: 'eraser',     desc: 'Удалить voxel' },
    { id: 'paint', label: 'Раскрасить', icon: 'palette',    desc: 'Перекрасить voxel в выбранный цвет' },
    { id: 'fill',  label: 'Заливка',    icon: 'sparkles',   desc: '3D Flood-fill: перекрасить всю связанную область' },
];

const MAX_TITLE_LEN = 100;

const ModelEditorScreen = ({ mode, userId, onClose, editingModelId = null }) => {
    const canvasRef = useRef(null);
    const sceneRef = useRef(null);

    const [tool, setTool] = useState('draw');
    const [color, setColor] = useState('#52b15a');
    // Режим материала: 'color' = solid vertex color, 'texture' = одна из VOXEL_TEXTURES
    const [matMode, setMatMode] = useState('color');
    const [textureId, setTextureId] = useState('grass');
    const [gridSize, setGridSize] = useState(32);
    const [brushSize, setBrushSize] = useState(1);
    // Рабочая плоскость для рисования "в пустоту": ось (X/Y/Z) и уровень 0..size-1
    const [planeAxis, setPlaneAxis] = useState('y');
    const [planeLevel, setPlaneLevel] = useState(0);
    const [voxelCount, setVoxelCount] = useState(0);
    const [title, setTitle] = useState('Новая модель');
    const [description, setDescription] = useState('');
    const [isPublic, setIsPublic] = useState(false);
    const [referenceVisible, setReferenceVisible] = useState(false);
    // Bumper для триггера ре-рендера канюки undo/redo (стеки внутри сцены).
    const [, setHistoryBump] = useState(0);
    const [isDirty, setIsDirty] = useState(false);
    // Индикатор загрузки существующей модели — пока true, поверх канваса
    // показываем оверлей со спиннером (иначе кажется что редактор завис).
    const [isLoadingModel, setIsLoadingModel] = useState(!!editingModelId);
    const [savedId, setSavedId] = useState(editingModelId || null);
    const [saveStatus, setSaveStatus] = useState('idle');  // 'idle' | 'dirty' | 'saving' | 'saved' | 'error'
    const [saveError, setSaveError] = useState('');
    // Модалка "Сохранить как новую модель" — открывается на первом сохранении.
    const [saveModalOpen, setSaveModalOpen] = useState(false);
    // Этап 6: процесс публикации модели в сообщество (true пока запрос идёт).
    const [publishBusy, setPublishBusy] = useState(false);
    // Таймер debounce для автосейва.
    const autoSaveTimerRef = useRef(null);

    // === Режим интерактивного создания превью ===
    // Пользователь жмёт "Создать превью" в модалке → модалка скрывается,
    // канвас затемняется, мышью выделяется КВАДРАТНАЯ область → она
    // кадрируется в 128×128 PNG и возвращается в модалку как обложка.
    const [previewMode, setPreviewMode] = useState(false);
    // Снимок канваса (PNG data URL + размеры рендера) — берётся при входе
    // в режим, из него потом вырезается выделенный квадрат.
    const previewSnapshotRef = useRef(null);   // { dataUrl, width, height }
    // Текущее выделение в КООРДИНАТАХ ЭКРАНА (CSS-пиксели относительно
    // overlay): { x, y, size } — null пока не начали тянуть.
    const [previewSel, setPreviewSel] = useState(null);
    // Идёт ли перетаскивание прямо сейчас.
    const previewDragRef = useRef(null);   // { startX, startY }
    // Ref на DOM-обёртку канваса — для расчёта координат мыши.
    const canvasWrapRef = useRef(null);
    // Превью, которое уже создано и ждёт применения в модалке.
    const pendingPreviewRef = useRef('');

    const isVoxel = mode === 'voxel';

    // === Инициализация Babylon-сцены (только для voxel) ===
    useEffect(() => {
        if (!isVoxel) return;
        if (!canvasRef.current) return;
        const scene = new VoxelModelScene(canvasRef.current, {
            gridSize: 32,
            initialColor: color,
            onChange: (count) => {
                setVoxelCount(count);
                setIsDirty(true);
                // Триггер ре-рендера чтобы Undo/Redo кнопки обновили disabled
                setHistoryBump((n) => n + 1);
            },
        });
        sceneRef.current = scene;
        return () => {
            try { scene.dispose(); } catch (e) {}
            sceneRef.current = null;
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [isVoxel]);

    // === Загрузка существующей модели для редактирования ===
    useEffect(() => {
        if (!isVoxel || !editingModelId) return;
        let cancelled = false;
        setIsLoadingModel(true);
        getUserModel(editingModelId, userId)
            .then(res => {
                if (cancelled) return;
                const m = res.data;
                if (m.kind !== 'voxel') { setIsLoadingModel(false); return; }
                setTitle(m.title || 'Новая модель');
                setDescription(m.description || '');
                setIsPublic(!!m.is_public);
                setSavedId(m.id);
                if (sceneRef.current && m.model_data) {
                    // deserialize синхронный и тяжёлый (десятки тысяч вокселей).
                    // Откладываем на следующий кадр через rAF — чтобы React
                    // успел отрисовать оверлей-спиннер ДО блокирующей работы,
                    // иначе пользователь видит "зависший" редактор.
                    requestAnimationFrame(() => {
                        if (cancelled || !sceneRef.current) { return; }
                        try {
                            sceneRef.current.deserialize(m.model_data);
                            setGridSize(sceneRef.current.gridSize);
                            setIsDirty(false);
                        } catch (e) {
                            console.error('[ModelEditor] deserialize failed:', e);
                            setSaveError('Не удалось разобрать данные модели');
                        } finally {
                            if (!cancelled) setIsLoadingModel(false);
                        }
                    });
                } else {
                    setIsLoadingModel(false);
                }
            })
            .catch(err => {
                console.error('[ModelEditor] load failed:', err);
                if (!cancelled) {
                    setSaveError('Не удалось загрузить модель');
                    setIsLoadingModel(false);
                }
            });
        return () => { cancelled = true; };
    }, [isVoxel, editingModelId, userId]);

    // === Sync пропсов в сцену ===
    useEffect(() => {
        sceneRef.current?.setTool(tool);
    }, [tool]);

    useEffect(() => {
        if (!sceneRef.current) return;
        if (matMode === 'texture') {
            sceneRef.current.setTexture(textureId);
        } else {
            sceneRef.current.setColor(color);
        }
    }, [matMode, color, textureId]);

    useEffect(() => {
        sceneRef.current?.setBrushSize(brushSize);
    }, [brushSize]);

    useEffect(() => {
        sceneRef.current?.setWorkPlaneAxis(planeAxis);
    }, [planeAxis]);

    useEffect(() => {
        sceneRef.current?.setWorkPlaneLevel(planeLevel);
    }, [planeLevel]);

    useEffect(() => {
        sceneRef.current?.setReferenceVisible(referenceVisible);
    }, [referenceVisible]);

    // Ctrl+Z / Ctrl+Y — undo/redo
    useEffect(() => {
        const onKey = (e) => {
            const tag = (e.target?.tagName || '').toLowerCase();
            if (tag === 'input' || tag === 'textarea') return;
            if (!e.ctrlKey && !e.metaKey) return;
            if (e.code === 'KeyZ' && !e.shiftKey) {
                e.preventDefault();
                handleUndo();
            } else if (e.code === 'KeyY' || (e.code === 'KeyZ' && e.shiftKey)) {
                e.preventDefault();
                handleRedo();
            }
        };
        window.addEventListener('keydown', onKey);
        return () => window.removeEventListener('keydown', onKey);
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    const handleUndo = useCallback(() => {
        if (sceneRef.current?.undo()) {
            setIsDirty(true);
            setHistoryBump((n) => n + 1);
        }
    }, []);

    const handleRedo = useCallback(() => {
        if (sceneRef.current?.redo()) {
            setIsDirty(true);
            setHistoryBump((n) => n + 1);
        }
    }, []);


    // === Действия ===
    const handleResize = (newSize) => {
        if (!sceneRef.current) return;
        if (voxelCount > 0) {
            // eslint-disable-next-line no-alert
            if (!window.confirm(`Изменение размера очистит все ${voxelCount} вокселей. Продолжить?`)) return;
        }
        sceneRef.current.resize(newSize);
        setGridSize(newSize);
        setVoxelCount(0);
        // Подвинем рабочую плоскость в середину новой сетки
        setPlaneLevel(Math.floor(newSize / 2));
        setIsDirty(true);
    };

    const handleClear = () => {
        if (!sceneRef.current) return;
        if (voxelCount === 0) return;
        // eslint-disable-next-line no-alert
        if (!window.confirm(`Удалить все ${voxelCount} вокселей?`)) return;
        sceneRef.current.clear();
        setIsDirty(true);
    };

    /**
     * Реальное обращение к API. Принимает override полей (title/description/
     * is_public/thumbnail) — модалка передаёт их при первом сохранении.
     * Без override используются текущие state-значения.
     */
    const performSave = useCallback(async (override = {}) => {
        if (!sceneRef.current) return false;
        if (!userId) {
            setSaveError('Войдите в аккаунт, чтобы сохранить');
            setSaveStatus('error');
            return false;
        }
        const t = (override.title ?? title).trim();
        if (!t) {
            setSaveError('Введите название модели');
            setSaveStatus('error');
            return false;
        }
        if (voxelCount === 0) {
            setSaveError('Поставьте хотя бы один voxel');
            setSaveStatus('error');
            return false;
        }
        setSaveStatus('saving');
        setSaveError('');
        try {
            const model_data = sceneRef.current.serialize();
            let thumbnail_b64 = override.thumbnail ?? '';
            if (!thumbnail_b64) {
                try { thumbnail_b64 = await sceneRef.current.captureThumbnail(128, 128); }
                catch (e) { /* ignore */ }
            }
            const payload = {
                title: t,
                description: override.description ?? description,
                kind: 'voxel',
                model_data,
                thumbnail_b64,
            };
            if (override.is_public !== undefined) {
                // Сначала пишем через create/update, потом отдельный publish-вызов
            }
            let res;
            const newPublicFlag = override.is_public ?? isPublic;
            if (savedId) {
                res = await updateUserModel(savedId, userId, payload);
            } else {
                res = await createUserModel(userId, payload);
                setSavedId(res.data.id);
            }
            // Синхронизируем флаг публичности отдельным запросом, если есть.
            const newId = res.data.id;
            if (newPublicFlag && !res.data.is_public) {
                try {
                    const { publishUserModel } = await import('../api/Kubikon3DService');
                    await publishUserModel(newId, userId);
                } catch (e) { /* ignore */ }
            } else if (!newPublicFlag && res.data.is_public) {
                try {
                    const { unpublishUserModel } = await import('../api/Kubikon3DService');
                    await unpublishUserModel(newId, userId);
                } catch (e) { /* ignore */ }
            }
            // Применяем override локально (модалка успешно сохранена)
            if (override.title !== undefined) setTitle(t);
            if (override.description !== undefined) setDescription(override.description);
            if (override.is_public !== undefined) setIsPublic(override.is_public);
            setSaveStatus('saved');
            setIsDirty(false);
            setTimeout(() => setSaveStatus((s) => s === 'saved' ? 'idle' : s), 2000);
            return true;
        } catch (err) {
            console.error('[ModelEditor] save failed:', err);
            setSaveError(err.response?.data?.error || 'Ошибка сохранения');
            setSaveStatus('error');
            return false;
        }
    }, [userId, title, description, isPublic, voxelCount, savedId]);

    /** Клик "Сохранить" в шапке.
     *  Всегда открывает модалку — и для новой модели, и для существующей.
     *  Так пользователь видит превью обложки и может пересоснять её
     *  («Снять снова»), а также правит название/описание/публичность.
     *  Тихое автосохранение (debounce) идёт отдельно, модалку не трогает. */
    const handleSaveClick = useCallback(() => {
        if (!sceneRef.current) return;
        if (voxelCount === 0) {
            setSaveError('Поставьте хотя бы один voxel');
            setSaveStatus('error');
            return;
        }
        setSaveModalOpen(true);
    }, [voxelCount]);

    /** Callback из модалки — пользователь подтвердил сохранение. */
    const handleSaveFromModal = useCallback(async (data) => {
        const ok = await performSave(data);
        if (ok) setSaveModalOpen(false);
    }, [performSave]);

    /** Этап 6.1: кнопка «Опубликовать» / «Снять с публикации» в шапке.
     *  Публиковать можно только сохранённую модель (нужен savedId).
     *  Публикация делает модель видимой всем в Toolbox → «Сообщество». */
    const handleTogglePublish = useCallback(async () => {
        if (!savedId || !userId || publishBusy) return;
        const goingPublic = !isPublic;
        // eslint-disable-next-line no-alert
        const ok = window.confirm(
            goingPublic
                ? 'Опубликовать модель? Она станет видна всем в разделе «Сообщество» — другие смогут ставить её в свои игры.'
                : 'Снять модель с публикации? Она исчезнет из «Сообщества», но останется у вас.',
        );
        if (!ok) return;
        setPublishBusy(true);
        try {
            if (goingPublic) {
                await publishUserModel(savedId, userId);
            } else {
                await unpublishUserModel(savedId, userId);
            }
            setIsPublic(goingPublic);
        } catch (err) {
            console.error('[ModelEditor] publish toggle failed:', err);
            // eslint-disable-next-line no-alert
            alert('Не удалось изменить публичность модели. Проверьте подключение.');
        } finally {
            setPublishBusy(false);
        }
    }, [savedId, userId, isPublic, publishBusy]);

    /** Захват превью для модалки сохранения (автоснимок при открытии). */
    const captureThumbForModal = useCallback(async () => {
        if (!sceneRef.current) return '';
        try {
            return await sceneRef.current.captureThumbnail(128, 128);
        } catch (e) { return ''; }
    }, []);

    // === Интерактивное создание превью ===

    /** Вход в режим выделения превью: прячем модалку, скрываем вспомогательные
     *  элементы сцены (рамка/пол/рабочая плоскость) и снимаем снимок канваса. */
    const enterPreviewMode = useCallback(async () => {
        if (!sceneRef.current) return;
        setSaveModalOpen(false);
        setPreviewSel(null);
        previewDragRef.current = null;
        // Скрываем рамку/пол/рабочую плоскость — чтобы пользователь видел на
        // канвасе ровно то, что попадёт в превью (и снимок был чистым).
        try { sceneRef.current.hideHelpersForPreview(); } catch (e) {}
        // Снимок канваса в его текущем виде — из него вырежем квадрат.
        try {
            const snap = await sceneRef.current.captureCanvasSnapshot();
            previewSnapshotRef.current = snap;
        } catch (e) {
            previewSnapshotRef.current = null;
        }
        setPreviewMode(true);
    }, []);

    /** Отмена режима выделения — возвращаемся в модалку без изменений. */
    const cancelPreviewMode = useCallback(() => {
        setPreviewMode(false);
        setPreviewSel(null);
        previewDragRef.current = null;
        previewSnapshotRef.current = null;
        // Возвращаем видимость рамки/пола/рабочей плоскости.
        try { sceneRef.current?.restoreHelpersAfterPreview(); } catch (e) {}
        setSaveModalOpen(true);
    }, []);

    /** Подтвердить выделение: вырезать квадрат → вернуть в модалку как превью. */
    const confirmPreviewSelection = useCallback(async () => {
        const snap = previewSnapshotRef.current;
        const sel = previewSel;
        const wrap = canvasWrapRef.current;
        if (!snap || !snap.dataUrl || !sel || !wrap) {
            cancelPreviewMode();
            return;
        }
        // Координаты выделения — в CSS-пикселях overlay. Снимок — в
        // рендер-пикселях (snap.width/height). Переводим через коэффициент.
        const rect = wrap.getBoundingClientRect();
        const scaleX = snap.width / rect.width;
        const scaleY = snap.height / rect.height;
        // Выделение квадратное в CSS — но из-за разных scaleX/scaleY в
        // снимке оно может стать прямоугольным. Берём минимальную сторону,
        // чтобы гарантированно вырезать квадрат внутри выделения.
        const sx = Math.round(sel.x * scaleX);
        const sy = Math.round(sel.y * scaleY);
        const sSize = Math.round(sel.size * Math.min(scaleX, scaleY));
        try {
            const VoxelModelSceneClass = sceneRef.current?.constructor;
            const cropped = VoxelModelSceneClass
                ? await VoxelModelSceneClass.cropSquareThumbnail(
                    snap.dataUrl, sx, sy, sSize, 128)
                : '';
            pendingPreviewRef.current = cropped || '';
        } catch (e) {
            pendingPreviewRef.current = '';
        }
        setPreviewMode(false);
        setPreviewSel(null);
        previewDragRef.current = null;
        previewSnapshotRef.current = null;
        // Возвращаем видимость рамки/пола/рабочей плоскости.
        try { sceneRef.current?.restoreHelpersAfterPreview(); } catch (e) {}
        setSaveModalOpen(true);
    }, [previewSel, cancelPreviewMode]);

    // --- Мышь в режиме выделения ---
    const onPreviewPointerDown = useCallback((e) => {
        if (!previewMode) return;
        const wrap = canvasWrapRef.current;
        if (!wrap) return;
        const rect = wrap.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;
        previewDragRef.current = { startX: x, startY: y };
        setPreviewSel({ x, y, size: 0 });
        try { wrap.setPointerCapture(e.pointerId); } catch (err) {}
    }, [previewMode]);

    const onPreviewPointerMove = useCallback((e) => {
        const drag = previewDragRef.current;
        if (!previewMode || !drag) return;
        const wrap = canvasWrapRef.current;
        if (!wrap) return;
        const rect = wrap.getBoundingClientRect();
        const curX = e.clientX - rect.left;
        const curY = e.clientY - rect.top;
        // Квадрат: сторона = max(|dx|,|dy|), направление — куда тянут.
        const dx = curX - drag.startX;
        const dy = curY - drag.startY;
        let size = Math.max(Math.abs(dx), Math.abs(dy));
        let x = dx < 0 ? drag.startX - size : drag.startX;
        let y = dy < 0 ? drag.startY - size : drag.startY;
        // Клампим выделение в границы overlay.
        if (x < 0) { size += x; x = 0; }
        if (y < 0) { size += y; y = 0; }
        if (x + size > rect.width) size = rect.width - x;
        if (y + size > rect.height) size = rect.height - y;
        size = Math.max(0, size);
        setPreviewSel({ x, y, size });
    }, [previewMode]);

    const onPreviewPointerUp = useCallback((e) => {
        if (!previewMode) return;
        previewDragRef.current = null;
        const wrap = canvasWrapRef.current;
        try { wrap?.releasePointerCapture(e.pointerId); } catch (err) {}
    }, [previewMode]);

    // === Esc — закрыть с подтверждением если dirty ===
    // Объявлено ПОСЛЕ preview-обработчиков: эффект ссылается на
    // cancelPreviewMode (const useCallback не поднимается hoisting'ом).
    useEffect(() => {
        const onKey = (e) => {
            if (e.key !== 'Escape') return;
            // В режиме выделения превью — Esc отменяет выделение, не редактор.
            if (previewMode) {
                cancelPreviewMode();
                return;
            }
            // Не закрываем редактор если открыта модалка — её закроет свой
            // обработчик через onClose.
            if (saveModalOpen) return;
            if (isDirty) {
                // eslint-disable-next-line no-alert
                if (!window.confirm('Есть несохранённые изменения. Выйти без сохранения?')) return;
            }
            onClose?.();
        };
        window.addEventListener('keydown', onKey);
        return () => window.removeEventListener('keydown', onKey);
    }, [onClose, isDirty, saveModalOpen, previewMode, cancelPreviewMode]);

    // === Автосейв ===
    // Срабатывает только если модель уже была сохранена (есть savedId).
    // Новые модели сохраняются только через модалку (требуется название).
    // Debounce — 2.5с после последнего изменения.
    useEffect(() => {
        if (!isDirty || !savedId || !userId) return;
        if (saveModalOpen) return;  // не дёргаем во время открытой модалки
        if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
        setSaveStatus((s) => s === 'idle' || s === 'saved' ? 'dirty' : s);
        autoSaveTimerRef.current = setTimeout(() => {
            autoSaveTimerRef.current = null;
            performSave();
        }, AUTOSAVE_DEBOUNCE_MS);
        return () => {
            if (autoSaveTimerRef.current) {
                clearTimeout(autoSaveTimerRef.current);
                autoSaveTimerRef.current = null;
            }
        };
    }, [isDirty, savedId, userId, saveModalOpen, performSave]);

    // === Smooth (mode !== 'voxel') — пока placeholder ===
    if (!isVoxel) {
        return (
            <div className={cl.screen}>
                <header className={cl.header}>
                    <button type="button" className={cl.backBtn} onClick={onClose}>
                        <Icon name="arrow-left" size={14} /> Назад
                    </button>
                    <div className={cl.titleBlock}>
                        <div className={cl.title}><Icon emoji="⚪" size={16} /> Редактор гладкой модели</div>
                        <div className={cl.subtitle}>Mesh из примитивов — как Roblox Studio</div>
                    </div>
                </header>
                <main className={cl.body}>
                    <div className={cl.placeholder}>
                        <div className={cl.placeholderIcon}><Icon emoji="⚪" size={48} /></div>
                        <div className={cl.placeholderTitle}>Гладкий редактор появится позже</div>
                        <div className={cl.placeholderText}>
                            Сейчас работает только воксельный редактор. Гладкая модель
                            (mesh из примитивов) — Этап 7 плана.
                        </div>
                    </div>
                </main>
            </div>
        );
    }

    // === Voxel-редактор ===
    return (
        <div className={cl.screen}>
            <header className={cl.header}>
                <button
                    type="button"
                    className={cl.backBtn}
                    onClick={() => {
                        if (isDirty) {
                            // eslint-disable-next-line no-alert
                            if (!window.confirm('Есть несохранённые изменения. Выйти без сохранения?')) return;
                        }
                        onClose?.();
                    }}
                    title="Назад в редактор сцены (Esc)"
                >
                    <Icon name="arrow-left" size={14} /> Назад
                </button>

                <div className={cl.titleBlock}>
                    <input
                        type="text"
                        className={cl.titleInput}
                        value={title}
                        onChange={(e) => { setTitle(e.target.value.slice(0, MAX_TITLE_LEN)); setIsDirty(true); }}
                        placeholder="Название модели"
                        maxLength={MAX_TITLE_LEN}
                    />
                    <div className={cl.subtitle}>
                        <Icon emoji="🟦" size={13} /> Воксельная · {voxelCount} вокс. · сетка {gridSize}³ · кисть {brushSize}³
                        {saveStatus === 'saving' && <span className={cl.savedNote}>· сохраняем...</span>}
                        {saveStatus === 'dirty' && <span className={cl.dirtyDot} title="Автосохранение через несколько секунд">•</span>}
                        {saveStatus === 'saved' && <span className={cl.savedNote}><Icon name="check" size={13} /> сохранено</span>}
                    </div>
                </div>

                <div className={cl.actions}>
                    <button
                        type="button"
                        className={cl.iconBtn}
                        onClick={handleUndo}
                        disabled={!sceneRef.current?.canUndo()}
                        title="Отменить (Ctrl+Z)"
                    >
                        <Icon name="arrow-left" size={14} /> Назад
                    </button>
                    <button
                        type="button"
                        className={cl.iconBtn}
                        onClick={handleRedo}
                        disabled={!sceneRef.current?.canRedo()}
                        title="Повторить (Ctrl+Y)"
                    >
                        <Icon name="arrow-right" size={14} /> Вперёд
                    </button>
                    <button
                        type="button"
                        className={`${cl.iconBtn} ${referenceVisible ? cl.iconBtnActive : ''}`}
                        onClick={() => setReferenceVisible(v => !v)}
                        title="Показать референс роста человека (2м = 8 вокселей)"
                    >
                        <Icon emoji="🧍" size={14} /> Масштаб
                    </button>
                    {saveStatus === 'error' && <span className={cl.errorNote}><Icon name="warning" size={13} /> {saveError}</span>}
                    <button
                        type="button"
                        className={cl.saveBtn}
                        onClick={handleSaveClick}
                        disabled={saveStatus === 'saving' || voxelCount === 0}
                    >
                        <Icon name="save" size={13} />
                        {saveStatus === 'saving' ? ' Сохранение...' : ' Сохранить'}
                    </button>
                    {/* Этап 6.1: публикация модели в сообщество.
                        Активна только для сохранённой модели (нужен savedId). */}
                    <button
                        type="button"
                        className={`${cl.iconBtn} ${isPublic ? cl.iconBtnActive : ''}`}
                        onClick={handleTogglePublish}
                        disabled={!savedId || publishBusy}
                        title={savedId
                            ? (isPublic
                                ? 'Снять модель с публикации'
                                : 'Опубликовать модель в «Сообщество»')
                            : 'Сначала сохраните модель'}
                    >
                        <Icon name={isPublic ? 'globe' : 'upload'} size={13} />
                        {publishBusy
                            ? ' …'
                            : (isPublic ? ' Опубликовано' : ' Опубликовать')}
                    </button>
                </div>
            </header>

            <div className={cl.workspace}>
                {/* === Левая панель: инструменты === */}
                <aside className={cl.sidebar}>
                    <div className={cl.section}>
                        <div className={cl.sectionTitle}>Инструменты</div>
                        <div className={cl.toolsGrid}>
                            {TOOLS.map(t => (
                                <button
                                    key={t.id}
                                    type="button"
                                    className={`${cl.toolBtn} ${tool === t.id ? cl.toolBtnActive : ''}`}
                                    onClick={() => setTool(t.id)}
                                    title={t.desc}
                                >
                                    <Icon name={t.icon} size={16} />
                                    <span>{t.label}</span>
                                </button>
                            ))}
                        </div>
                    </div>

                    <div className={cl.section}>
                        <div className={cl.matModeTabs}>
                            <button
                                type="button"
                                className={`${cl.matModeTab} ${matMode === 'color' ? cl.matModeTabActive : ''}`}
                                onClick={() => setMatMode('color')}
                            >
                                <Icon emoji="🎨" size={14} /> Цвет
                            </button>
                            <button
                                type="button"
                                className={`${cl.matModeTab} ${matMode === 'texture' ? cl.matModeTabActive : ''}`}
                                onClick={() => setMatMode('texture')}
                            >
                                <Icon emoji="🧱" size={14} /> Текстура
                            </button>
                        </div>

                        {matMode === 'color' && (
                            <>
                                <div className={cl.paletteGrid}>
                                    {DEFAULT_PALETTE.map(c => (
                                        <button
                                            key={c}
                                            type="button"
                                            className={`${cl.colorSwatch} ${color === c ? cl.colorSwatchActive : ''}`}
                                            style={{ background: c }}
                                            onClick={() => setColor(c)}
                                            title={c}
                                        />
                                    ))}
                                </div>
                                <div className={cl.colorPickerRow}>
                                    <input
                                        type="color"
                                        value={color}
                                        onChange={(e) => setColor(e.target.value)}
                                        className={cl.colorPicker}
                                        title="Свой цвет"
                                    />
                                    <span className={cl.colorHex}>{color}</span>
                                </div>
                            </>
                        )}

                        {matMode === 'texture' && (
                            <div className={cl.textureGrid}>
                                {VOXEL_TEXTURES.map(t => (
                                    <button
                                        key={t.id}
                                        type="button"
                                        className={`${cl.textureSwatch} ${textureId === t.id ? cl.textureSwatchActive : ''}`}
                                        onClick={() => setTextureId(t.id)}
                                        title={t.label}
                                    >
                                        <img src={t.file} alt={t.label} />
                                        <span>{t.label}</span>
                                    </button>
                                ))}
                            </div>
                        )}
                    </div>

                    <div className={cl.section}>
                        <div className={cl.sectionTitle}>Размер кисти</div>
                        <div className={cl.sizeRow}>
                            {[1, 2, 3, 4, 5].map(n => (
                                <button
                                    key={n}
                                    type="button"
                                    className={`${cl.sizeBtn} ${brushSize === n ? cl.sizeBtnActive : ''}`}
                                    onClick={() => setBrushSize(n)}
                                    title={`Кисть ${n}×${n}×${n} вокселей за клик`}
                                >
                                    {n}³
                                </button>
                            ))}
                        </div>
                        <div className={cl.hintText}>
                            Большая кисть = «толстый блок» — используй разные размеры
                            для разных частей модели (как стволы деревьев).
                        </div>
                    </div>

                    <div className={cl.section}>
                        <div className={cl.sectionTitle}>
                            Рабочая плоскость · <span className={cl.muted}>{planeAxis.toUpperCase()}={planeLevel}</span>
                        </div>
                        <div className={cl.axisRow}>
                            {['x', 'y', 'z'].map(a => (
                                <button
                                    key={a}
                                    type="button"
                                    className={`${cl.axisBtn} ${planeAxis === a ? cl.axisBtnActive : ''}`}
                                    onClick={() => {
                                        setPlaneAxis(a);
                                        setPlaneLevel(Math.floor(gridSize / 2));
                                    }}
                                    title={`Плоскость перпендикулярна оси ${a.toUpperCase()}`}
                                >
                                    {a.toUpperCase()}
                                </button>
                            ))}
                        </div>
                        <input
                            type="range"
                            min="0"
                            max={gridSize - 1}
                            step="1"
                            value={planeLevel}
                            onChange={(e) => setPlaneLevel(Number(e.target.value))}
                            className={cl.slider}
                        />
                        <div className={cl.hintText}>
                            Клик по синей плоскости ставит voxel на её уровне.
                            Меняй ось чтобы рисовать сбоку (X/Z) или сверху (Y),
                            ползунок — чтобы выбрать конкретный срез.
                        </div>
                    </div>

                    <div className={cl.section}>
                        <div className={cl.sectionTitle}>Размер сетки</div>
                        <div className={cl.sizeRow}>
                            {ALLOWED_GRID_SIZES.map(s => (
                                <button
                                    key={s}
                                    type="button"
                                    className={`${cl.sizeBtn} ${gridSize === s ? cl.sizeBtnActive : ''}`}
                                    onClick={() => handleResize(s)}
                                >
                                    {s}³
                                </button>
                            ))}
                        </div>
                    </div>

                    <div className={cl.section}>
                        <button
                            type="button"
                            className={cl.dangerBtn}
                            onClick={handleClear}
                            disabled={voxelCount === 0}
                        >
                            <Icon name="trash" size={13} /> Очистить
                        </button>
                    </div>

                    <div className={cl.hintBlock}>
                        <div><b>ЛКМ</b> — рисовать</div>
                        <div><b>Shift+ЛКМ</b> — стереть</div>
                        <div><b>ПКМ</b> — повернуть камеру</div>
                        <div><b>ПКМ + WASD</b> — летать</div>
                        <div><b>ПКМ + Q/E</b> — вниз/вверх</div>
                        <div><b>ПКМ + Shift</b> — быстрее</div>
                        <div><b>Колесо</b> — приблизить</div>
                        <div><b>F</b> — центрировать</div>
                        <div><b>Ctrl+Z/Y</b> — отмена/повтор</div>
                        <div><b>Esc</b> — выйти</div>
                    </div>
                </aside>

                {/* === Babylon canvas === */}
                <div
                    className={cl.canvasWrap}
                    ref={canvasWrapRef}
                    onPointerDown={previewMode ? onPreviewPointerDown : undefined}
                    onPointerMove={previewMode ? onPreviewPointerMove : undefined}
                    onPointerUp={previewMode ? onPreviewPointerUp : undefined}
                >
                    <canvas ref={canvasRef} className={cl.canvas} />
                    {/* Оверлей загрузки модели — пока deserialize не закончился,
                        чтобы редактор не выглядел "зависшим". */}
                    {isLoadingModel && (
                        <div className={cl.loadingOverlay}>
                            <div className={cl.loadingSpinner} />
                            <div className={cl.loadingText}>Загрузка модели…</div>
                        </div>
                    )}
                    {/* Оверлей выделения превью: затемнение + квадратное
                        выделение мышью. Внутри выделения затемнение убрано
                        (модель видна чисто) — через box-shadow на рамке. */}
                    {previewMode && (
                        <div className={cl.previewOverlay}>
                            {/* Затемняющий слой — показан только пока ничего
                                не выделено. Когда есть выделение, используем
                                рамку с огромной тенью (вырез "окошка"). */}
                            {(!previewSel || previewSel.size < 4) && (
                                <div className={cl.previewDim} />
                            )}
                            {previewSel && previewSel.size >= 4 && (
                                <div
                                    className={cl.previewSelBox}
                                    style={{
                                        left: previewSel.x,
                                        top: previewSel.y,
                                        width: previewSel.size,
                                        height: previewSel.size,
                                    }}
                                />
                            )}
                            <div className={cl.previewHint}>
                                {previewSel && previewSel.size >= 4
                                    ? 'Отпусти мышь — затем нажми «Создать»'
                                    : 'Выдели квадратную область для превью'}
                            </div>
                            <div
                                className={cl.previewActions}
                                onPointerDown={(e) => e.stopPropagation()}
                            >
                                <button
                                    type="button"
                                    className={cl.previewCancelBtn}
                                    onClick={cancelPreviewMode}
                                >
                                    Отмена
                                </button>
                                <button
                                    type="button"
                                    className={cl.previewCreateBtn}
                                    onClick={confirmPreviewSelection}
                                    disabled={!previewSel || previewSel.size < 8}
                                >
                                    Создать
                                </button>
                            </div>
                        </div>
                    )}
                </div>
            </div>

            {/* Модалка "Сохранить как новую модель" — открывается на первое
                сохранение и при каждом клике "Сохранить".
                initial.thumbnail — превью, созданное через интерактивное
                выделение (enterPreviewMode → confirmPreviewSelection).
                Если оно есть, модалка покажет его вместо автоснимка. */}
            <ModelSaveModal
                open={saveModalOpen}
                initial={{
                    title,
                    description,
                    is_public: isPublic,
                    thumbnail: pendingPreviewRef.current || '',
                }}
                onClose={() => {
                    pendingPreviewRef.current = '';
                    setSaveModalOpen(false);
                }}
                onSave={(data) => {
                    pendingPreviewRef.current = '';
                    return handleSaveFromModal(data);
                }}
                onCaptureThumbnail={captureThumbForModal}
                onCreatePreview={enterPreviewMode}
                mode={savedId ? 'edit' : 'create'}
            />
        </div>
    );
};

export default ModelEditorScreen;
