/**
 * VH Posting System - Frontend JavaScript
 * Apple Style UI | Multi-platform posting
 */

document.addEventListener('DOMContentLoaded', function() {
    // ============ THEME MANAGEMENT ============

    function initTheme() {
        const savedTheme = localStorage.getItem('theme') || 'light';
        applyTheme(savedTheme);
    }

    function applyTheme(theme) {
        if (theme === 'dark') {
            document.documentElement.setAttribute('data-theme', 'dark');
            document.getElementById('theme-toggle')?.classList.add('dark');
        } else {
            document.documentElement.removeAttribute('data-theme');
            document.getElementById('theme-toggle')?.classList.remove('dark');
        }
        localStorage.setItem('theme', theme);
    }

    function toggleTheme() {
        const currentTheme = localStorage.getItem('theme') || 'light';
        const newTheme = currentTheme === 'light' ? 'dark' : 'light';
        applyTheme(newTheme);
    }

    document.getElementById('theme-toggle')?.addEventListener('click', toggleTheme);
    document.getElementById('btn-toggle-theme')?.addEventListener('click', toggleTheme);
    initTheme();

    // ============ CROSS-PROMO SETTINGS ============

    const CROSS_PROMO_KEY = 'vh_cross_promo';

    function getCrossPromoSettings() {
        try {
            const data = localStorage.getItem(CROSS_PROMO_KEY);
            return data ? JSON.parse(data) : {
                telegramLink: '',
                vkLink: '',
                textForTg: 'Мой канал ВКонтакте',
                textForVk: 'Мой канал в Telegram'
            };
        } catch (e) {
            return { telegramLink: '', vkLink: '', textForTg: 'Мой канал ВКонтакте', textForVk: 'Мой канал в Telegram' };
        }
    }

    function saveCrossPromoSettings(settings) {
        localStorage.setItem(CROSS_PROMO_KEY, JSON.stringify(settings));
    }

    async function initCrossPromoSettings() {
        // Try to load from server first, fallback to localStorage
        try {
            const formData = new FormData();
            formData.append('action', 'get_cross_promo');
            const response = await fetch('api.php', { method: 'POST', body: formData });
            const data = await response.json();
            if (data.settings) {
                saveCrossPromoSettings(data.settings);
            }
        } catch (e) {
            console.log('Using local cross-promo settings');
        }

        const settings = getCrossPromoSettings();
        const tgInput = document.getElementById('cross-promo-telegram');
        const vkInput = document.getElementById('cross-promo-vk');
        const textTgInput = document.getElementById('cross-promo-text-tg');
        const textVkInput = document.getElementById('cross-promo-text-vk');

        if (tgInput) tgInput.value = settings.telegramLink || '';
        if (vkInput) vkInput.value = settings.vkLink || '';
        if (textTgInput) textTgInput.value = settings.textForTg || 'Мой канал ВКонтакте';
        if (textVkInput) textVkInput.value = settings.textForVk || 'Мой канал в Telegram';
    }

    document.getElementById('btn-save-cross-promo')?.addEventListener('click', async () => {
        const settings = {
            telegramLink: document.getElementById('cross-promo-telegram')?.value.trim() || '',
            vkLink: document.getElementById('cross-promo-vk')?.value.trim() || '',
            textForTg: document.getElementById('cross-promo-text-tg')?.value.trim() || 'Мой канал ВКонтакте',
            textForVk: document.getElementById('cross-promo-text-vk')?.value.trim() || 'Мой канал в Telegram'
        };

        // Save to localStorage
        saveCrossPromoSettings(settings);

        // Save to server for cron
        try {
            const formData = new FormData();
            formData.append('action', 'save_cross_promo');
            formData.append('telegramLink', settings.telegramLink);
            formData.append('vkLink', settings.vkLink);
            formData.append('textForTg', settings.textForTg);
            formData.append('textForVk', settings.textForVk);
            await fetch('api.php', { method: 'POST', body: formData });
        } catch (e) {
            console.warn('Failed to save cross-promo to server:', e);
        }

        const status = document.getElementById('cross-promo-save-status');
        if (status) {
            status.textContent = '✓ Сохранено';
            status.className = 'save-status success';
            setTimeout(() => { status.textContent = ''; }, 2000);
        }
        showNotification('Настройки кросс-промо сохранены', 'success');
    });

    // Initialize cross-promo on load
    initCrossPromoSettings();

    // ============ AUTO-SAVE POST DRAFT (SERVER-BASED) ============

    let serverDraft = null;
    let draftSaveTimer = null;

    // Save draft to server (debounced - for text input)
    function savePostDraft() {
        clearTimeout(draftSaveTimer);
        draftSaveTimer = setTimeout(savePostDraftToServer, 1500);
    }

    // Save draft immediately (for tags, photos)
    function savePostDraftNow() {
        clearTimeout(draftSaveTimer);
        savePostDraftToServer();
    }

    // Actually save to server
    async function savePostDraftToServer() {
        const postText = document.getElementById('post-text')?.value || '';
        const tags = typeof tagContexts !== 'undefined' ? tagContexts.post : [];
        const photos = state?.selectedPhotos?.map(p => ({
            id: p.id,
            title: p.title,
            urls: p.urls,
            page_url: p.page_url,
            is_video: p.is_video
        })) || [];
        const uploadedFiles = state?.uploadedFiles?.filter(f => !f.uploading && f.url).map(f => ({
            id: f.id,
            name: f.name,
            type: f.type,
            url: f.url
        })) || [];

        const formData = new FormData();
        formData.append('action', 'save_draft');
        formData.append('text', postText);
        formData.append('tags', JSON.stringify(tags));
        formData.append('photos', JSON.stringify(photos));
        formData.append('uploaded_files', JSON.stringify(uploadedFiles));

        try {
            await fetch('api.php', { method: 'POST', body: formData });
        } catch (e) {
            console.warn('Could not save draft to server:', e);
        }
    }

    // Load draft from server
    async function loadPostDraftFromServer() {
        try {
            const response = await fetch('api.php?action=get_draft');
            const data = await response.json();
            if (data.success && data.draft) {
                return data.draft;
            }
        } catch (e) {
            console.error('Error loading draft from server:', e);
        }
        return null;
    }

    // Clear draft on server
    async function clearPostDraftOnServer() {
        try {
            const formData = new FormData();
            formData.append('action', 'clear_draft');
            await fetch('api.php', { method: 'POST', body: formData });
        } catch (e) {
            console.warn('Could not clear draft on server:', e);
        }
    }

    function clearPostDraft() {
        clearPostDraftOnServer();
    }

    // Save photos and uploaded files immediately
    function saveSelectedPhotos() {
        savePostDraftNow();
    }

    function saveUploadedFiles() {
        savePostDraftNow();
    }

    // Auto-save on text input (debounced)
    document.getElementById('post-text')?.addEventListener('input', () => {
        savePostDraft();
    });

    // ============ STATE ============

    // Soft limit for safe cross-platform posting. Can be lifted with the
    // "Больше 9 фото" toggle on the posting page; VK gets a confirm warning
    // because its album endpoint only accepts up to 9 attachments.
    const DEFAULT_MAX_PHOTOS = 9;
    const HARD_MAX_PHOTOS = 99;

    function isPhotoLimitUnlocked() {
        return localStorage.getItem('vh_unlock_9') === '1';
    }

    function getMaxPhotos() {
        return isPhotoLimitUnlocked() ? HARD_MAX_PHOTOS : DEFAULT_MAX_PHOTOS;
    }

    // Backward compat for code that still reads MAX_PHOTOS as a constant —
    // we keep the name but evaluate dynamically through getter usage below.
    const MAX_PHOTOS = DEFAULT_MAX_PHOTOS;

    // Sync the "more than 9 photos" toggle with localStorage and refresh counters
    // whenever it changes.
    function bindPhotoLimitToggle() {
        const chk = document.getElementById('chk-unlock-photo-limit');
        if (!chk) return;
        chk.checked = isPhotoLimitUnlocked();
        chk.addEventListener('change', () => {
            if (chk.checked) {
                localStorage.setItem('vh_unlock_9', '1');
            } else {
                localStorage.removeItem('vh_unlock_9');
                // If currently over 9 photos, warn — we won't trim automatically.
                if (getTotalPhotosCount() > DEFAULT_MAX_PHOTOS) {
                    showNotification(`Сейчас выбрано ${getTotalPhotosCount()} фото. Удалите лишние перед публикацией.`, 'info');
                }
            }
            updateSelectionUI();
            updatePostingPreview();
        });
    }
    // Bind after DOM is ready — we're already inside DOMContentLoaded.
    bindPhotoLimitToggle();

    function getTotalPhotosCount() {
        return state.selectedPhotos.length + state.uploadedFiles.length;
    }

    function canAddPhotos(count = 1) {
        const max = getMaxPhotos();
        if (getTotalPhotosCount() + count > max) {
            showNotification(`Максимум ${max} фото/видео`, 'error');
            return false;
        }
        return true;
    }

    const state = {
        selectedPhotos: [],
        uploadedFiles: [],
        currentPage: 1,
        totalPages: 1,
        currentAlbum: '',
        isLoadingPhotos: false,
        isLoadingAlbums: false,
        photoRequestId: 0,      // For request deduplication
        albumRequestId: 0,
        // Infinite scroll state for albums
        albumsPage: 1,
        albumsTotalPages: 1,
        albumsTotal: 0,
        isLoadingMoreAlbums: false,
        allAlbums: [],
        // Infinite scroll state for photos
        isLoadingMorePhotos: false,
        allPhotos: []
    };

    // Load draft from server on page load
    let pendingDraft = null;
    loadPostDraftFromServer().then(draft => {
        if (!draft) return;
        serverDraft = draft;
        pendingDraft = draft;

        // Restore text
        const postText = document.getElementById('post-text');
        if (postText && draft.text) {
            postText.value = draft.text;
        }

        // Restore photos
        if (draft.photos && draft.photos.length > 0) {
            state.selectedPhotos = draft.photos;
        }

        // Restore uploaded files (without dataUrl, just URL)
        if (draft.uploaded_files && draft.uploaded_files.length > 0) {
            state.uploadedFiles = draft.uploaded_files.map(f => ({
                ...f,
                dataUrl: f.url, // Use URL as dataUrl for preview
                uploading: false
            }));
        }

        // Update preview
        updatePostingPreview();

        // Try to restore tags (will work if tagContexts is already initialized)
        restoreDraftTags();
    });

    // Function to restore tags after tagContexts is available
    let tagsRestored = false;
    function restoreDraftTags() {
        if (tagsRestored) return;
        if (!pendingDraft || !pendingDraft.tags || pendingDraft.tags.length === 0) return;
        if (typeof tagContexts === 'undefined') return;

        tagsRestored = true;
        tagContexts.post = pendingDraft.tags;
        const tagsList = document.getElementById('post-tags-list');
        if (tagsList) {
            tagsList.innerHTML = pendingDraft.tags.map(tag => `
                <span class="tag-chip">
                    #${escapeHtml(tag)}
                    <button type="button" class="tag-remove" data-tag="${escapeHtml(tag)}">&times;</button>
                </span>
            `).join('');
            // Re-attach remove handlers
            tagsList.querySelectorAll('.tag-remove').forEach(btn => {
                btn.addEventListener('click', () => {
                    const tag = btn.dataset.tag;
                    tagContexts.post = tagContexts.post.filter(t => t !== tag);
                    btn.parentElement.remove();
                    savePostDraftNow();
                });
            });
        }
        showNotification('Черновик восстановлен', 'info');
    }

    // ============ PHOTO SOURCE BUTTONS ============

    // Select from Flickr - go to gallery tab
    document.getElementById('btn-select-from-flickr')?.addEventListener('click', () => {
        document.querySelector('.nav-btn[data-tab="gallery"]')?.click();
    });

    // Upload from device
    document.getElementById('btn-upload-files')?.addEventListener('click', () => {
        document.getElementById('file-upload')?.click();
    });

    document.getElementById('file-upload')?.addEventListener('change', async (e) => {
        const files = Array.from(e.target.files);
        if (!files.length) return;

        for (const file of files) {
            // Check photo limit
            const maxNow = getMaxPhotos();
            if (getTotalPhotosCount() >= maxNow) {
                showNotification(`Максимум ${maxNow} фото/видео`, 'error');
                break;
            }

            // Check file size (max 50MB)
            if (file.size > 50 * 1024 * 1024) {
                showNotification(`Файл ${file.name} слишком большой (макс 50MB)`, 'error');
                continue;
            }

            // Create preview immediately
            const reader = new FileReader();
            reader.onload = async (event) => {
                const fileId = 'upload_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
                const fileData = {
                    id: fileId,
                    name: file.name,
                    type: file.type,
                    size: file.size,
                    dataUrl: event.target.result,
                    uploading: true,
                    url: null
                };
                state.uploadedFiles.push(fileData);
                renderUploadedFiles();

                // Upload to server
                try {
                    const formData = new FormData();
                    formData.append('action', 'upload_file');
                    formData.append('file', file);

                    const response = await fetch('api.php', { method: 'POST', body: formData });
                    const data = await response.json();

                    // Find and update the file in state
                    const idx = state.uploadedFiles.findIndex(f => f.id === fileId);
                    if (idx !== -1) {
                        if (data.error) {
                            showNotification(`Ошибка загрузки ${file.name}: ${data.error}`, 'error');
                            state.uploadedFiles.splice(idx, 1);
                        } else {
                            state.uploadedFiles[idx].url = data.url;
                            state.uploadedFiles[idx].uploading = false;
                            saveUploadedFiles(); // Save to localStorage after successful upload
                        }
                        renderUploadedFiles();
                    }
                } catch (error) {
                    showNotification(`Ошибка загрузки ${file.name}`, 'error');
                    const idx = state.uploadedFiles.findIndex(f => f.id === fileId);
                    if (idx !== -1) {
                        state.uploadedFiles.splice(idx, 1);
                        renderUploadedFiles();
                    }
                }
            };
            reader.readAsDataURL(file);
        }

        // Clear input for re-upload of same files
        e.target.value = '';
    });

    function renderUploadedFiles() {
        // Now uses the combined preview
        updatePostingPreview();
    }

    // Drag and drop support for combined preview
    const combinedPreview = document.getElementById('post-photos-preview');
    if (combinedPreview) {
        ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
            combinedPreview.addEventListener(eventName, (e) => {
                e.preventDefault();
                e.stopPropagation();
            });
        });

        ['dragenter', 'dragover'].forEach(eventName => {
            combinedPreview.addEventListener(eventName, () => {
                combinedPreview.classList.add('drag-over');
            });
        });

        ['dragleave', 'drop'].forEach(eventName => {
            combinedPreview.addEventListener(eventName, () => {
                combinedPreview.classList.remove('drag-over');
            });
        });

        combinedPreview.addEventListener('drop', (e) => {
            const files = e.dataTransfer.files;
            if (files.length) {
                const fileInput = document.getElementById('file-upload');
                if (fileInput) {
                    fileInput.files = files;
                    fileInput.dispatchEvent(new Event('change'));
                }
            }
        });
    }

    // AbortController for cancelling in-flight requests
    let photoAbortController = null;
    let albumAbortController = null;

    // ============ ALBUM PREFERENCES (localStorage) ============

    const ALBUM_CACHE_KEY = 'vh_album_cache';
    const ALBUM_PREFS_KEY = 'vh_album_prefs';
    const CACHE_TTL = 60 * 60 * 1000; // 1 hour

    function getAlbumCache() {
        try {
            const cached = localStorage.getItem(ALBUM_CACHE_KEY);
            if (!cached) return null;
            const data = JSON.parse(cached);
            if (Date.now() - data.timestamp > CACHE_TTL) {
                localStorage.removeItem(ALBUM_CACHE_KEY);
                return null;
            }
            // Handle both old format (array) and new format (object with albums property)
            const albums = data.albums;
            if (Array.isArray(albums)) {
                return albums;
            } else if (albums && Array.isArray(albums.albums)) {
                return albums.albums;
            }
            return null;
        } catch (e) {
            return null;
        }
    }

    function setAlbumCache(albumsOrData) {
        try {
            // Accept either array or object with albums property
            const albums = Array.isArray(albumsOrData) ? albumsOrData : (albumsOrData.albums || []);
            localStorage.setItem(ALBUM_CACHE_KEY, JSON.stringify({
                albums: albums,
                timestamp: Date.now()
            }));
        } catch (e) {
            console.warn('Failed to cache albums:', e);
        }
    }

    function getAlbumPrefs() {
        try {
            const prefs = localStorage.getItem(ALBUM_PREFS_KEY);
            return prefs ? JSON.parse(prefs) : { favorites: [], order: [] };
        } catch (e) {
            return { favorites: [], order: [] };
        }
    }

    function saveAlbumPrefs(prefs) {
        try {
            localStorage.setItem(ALBUM_PREFS_KEY, JSON.stringify(prefs));
        } catch (e) {
            console.warn('Failed to save album prefs:', e);
        }
    }

    function toggleAlbumFavorite(albumId) {
        const prefs = getAlbumPrefs();
        const index = prefs.favorites.indexOf(albumId);
        if (index === -1) {
            prefs.favorites.push(albumId);
        } else {
            prefs.favorites.splice(index, 1);
        }
        saveAlbumPrefs(prefs);
        renderAlbumDropdown(window._cachedAlbums || []);
    }

    function sortAlbumsByPreference(albums) {
        const prefs = getAlbumPrefs();
        const favorites = new Set(prefs.favorites);

        // Sort: favorites first, then rest
        return [...albums].sort((a, b) => {
            const aFav = favorites.has(a.id);
            const bFav = favorites.has(b.id);
            if (aFav && !bFav) return -1;
            if (!aFav && bFav) return 1;
            return 0; // Keep original order within groups
        });
    }

    // ============ DOM ELEMENTS ============

    const selectionBar = document.getElementById('selection-bar');
    const selectedCountEl = document.getElementById('selected-count');
    const photoGallery = document.getElementById('photo-gallery');
    const postPhotosPreview = document.getElementById('post-photos-preview');

    // ============ UTILITY FUNCTIONS ============

    function escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text || '';
        return div.innerHTML;
    }

    function showNotification(message, type = 'info') {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 16px 24px;
            border-radius: 12px;
            font-weight: 500;
            z-index: 10000;
            animation: slideIn 0.3s ease;
            backdrop-filter: blur(20px);
            -webkit-backdrop-filter: blur(20px);
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
            color: white;
            background: ${type === 'success' ? 'rgba(52, 199, 89, 0.9)' :
                         type === 'error' ? 'rgba(255, 59, 48, 0.9)' :
                         'rgba(0, 122, 255, 0.9)'};
        `;
        document.body.appendChild(notification);
        setTimeout(() => {
            notification.style.animation = 'slideOut 0.3s ease';
            setTimeout(() => notification.remove(), 300);
        }, 3000);
    }

    // Add animation styles
    const style = document.createElement('style');
    style.textContent = `
        @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
        @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
    `;
    document.head.appendChild(style);

    // ============ SELECTION MANAGEMENT ============

    function updateSelectionUI() {
        const count = state.selectedPhotos.length;
        const total = getTotalPhotosCount();
        const maxNow = getMaxPhotos();

        // Update counter with limit info (gallery floating bar)
        if (selectedCountEl) {
            selectedCountEl.textContent = `${total}/${maxNow}`;
        }

        // Update counter on posting page
        const photoCounter = document.getElementById('photo-counter');
        if (photoCounter) {
            photoCounter.textContent = `${total}/${maxNow}`;
            photoCounter.classList.toggle('at-limit', total >= maxNow);
        }

        // Show/hide floating action bar
        if (selectionBar) {
            if (count > 0) {
                selectionBar.classList.remove('hidden');
            } else {
                selectionBar.classList.add('hidden');
            }
        }
    }

    function updatePostingPreview() {
        // Always update photo counter
        const total = getTotalPhotosCount();
        const maxNow = getMaxPhotos();
        const photoCounter = document.getElementById('photo-counter');
        if (photoCounter) {
            photoCounter.textContent = `${total}/${maxNow}`;
            photoCounter.classList.toggle('at-limit', total >= maxNow);
        }

        if (!postPhotosPreview) return;

        const hasFlickrPhotos = state.selectedPhotos.length > 0;
        const hasUploadedFiles = state.uploadedFiles.length > 0;

        if (!hasFlickrPhotos && !hasUploadedFiles) {
            postPhotosPreview.innerHTML = '<p class="placeholder">Нажмите кнопку выше чтобы добавить фото</p>';
            return;
        }

        postPhotosPreview.innerHTML = '';

        // Render Flickr photos
        state.selectedPhotos.forEach((photo, index) => {
            const div = document.createElement('div');
            div.className = 'preview-thumb';
            div.innerHTML = `
                <img src="${photo.urls.small || photo.urls.medium || photo.urls.square}" alt="${escapeHtml(photo.title)}">
                <button class="remove-btn" data-index="${index}" data-source="flickr">×</button>
                <span class="source-badge">flickr</span>
            `;
            postPhotosPreview.appendChild(div);
        });

        // Render uploaded files
        state.uploadedFiles.forEach((file) => {
            const div = document.createElement('div');
            const isVideo = file.type.startsWith('video/');
            div.className = 'preview-thumb' + (file.uploading ? ' uploading' : '');
            div.innerHTML = `
                ${isVideo
                    ? `<video src="${file.dataUrl}"></video><span class="video-badge">▶</span>`
                    : `<img src="${file.dataUrl}" alt="${escapeHtml(file.name)}">`
                }
                ${file.uploading ? '<div class="upload-spinner"></div>' : ''}
                <button class="remove-btn" data-id="${file.id}" data-source="upload">×</button>
                <span class="source-badge">файл</span>
            `;
            postPhotosPreview.appendChild(div);
        });

        // Attach remove handlers for Flickr photos
        postPhotosPreview.querySelectorAll('.remove-btn[data-source="flickr"]').forEach(btn => {
            btn.addEventListener('click', (e) => {
                e.stopPropagation();
                const index = parseInt(btn.dataset.index);
                state.selectedPhotos.splice(index, 1);
                updatePostingPreview();
                updateSelectionUI();
                syncGallerySelection();
                saveSelectedPhotos();
            });
        });

        // Attach remove handlers for uploaded files
        postPhotosPreview.querySelectorAll('.remove-btn[data-source="upload"]').forEach(btn => {
            btn.addEventListener('click', (e) => {
                e.stopPropagation();
                const id = btn.dataset.id;
                state.uploadedFiles = state.uploadedFiles.filter(f => f.id !== id);
                updatePostingPreview();
                saveUploadedFiles();
            });
        });
    }

    function syncGallerySelection() {
        document.querySelectorAll('.photo-item').forEach(item => {
            const photoId = item.dataset.photoId;
            if (state.selectedPhotos.find(p => p.id === photoId)) {
                item.classList.add('selected');
            } else {
                item.classList.remove('selected');
            }
        });
    }

    // ============ TAB NAVIGATION ============

    const navBtns = document.querySelectorAll('.nav-btn');
    const tabContents = document.querySelectorAll('.tab-content');

    navBtns.forEach(btn => {
        btn.addEventListener('click', () => {
            const tabId = btn.dataset.tab;

            navBtns.forEach(b => b.classList.remove('active'));
            tabContents.forEach(t => t.classList.remove('active'));

            btn.classList.add('active');
            document.getElementById('tab-' + tabId)?.classList.add('active');

            // Load data when switching tabs
            if (tabId === 'gallery') {
                // Albums are loaded on button click now
                // But if we have cache, render it
                if (window._cachedAlbums && albumsGrid) {
                    renderAlbumsGrid(window._cachedAlbums);
                }
            } else if (tabId === 'posting') {
                updatePostingPreview();
                loadTelegramStatus();
                loadVKStatus();
            } else if (tabId === 'settings') {
                loadTelegramStatus();
                loadVKStatus();
            }
        });
    });

    // ============ LINK CONVERTER ============

    const btnConvert = document.getElementById('btn-convert');
    const btnCopy = document.getElementById('btn-copy');
    const inputUrls = document.getElementById('input-urls');
    const outputResult = document.getElementById('output-result');

    btnConvert?.addEventListener('click', async () => {
        const urls = inputUrls?.value.trim();
        if (!urls) {
            showNotification('Введите ссылки Flickr', 'error');
            return;
        }

        btnConvert.disabled = true;
        btnConvert.textContent = 'Конвертация...';

        try {
            const formData = new FormData();
            formData.append('action', 'convert');
            formData.append('urls', urls);
            formData.append('size', document.getElementById('image-size')?.value || 'Large');
            formData.append('format', document.getElementById('output-format')?.value || 'bbcode');

            const response = await fetch('api.php', { method: 'POST', body: formData });
            const data = await response.json();

            if (data.error) {
                showNotification('Ошибка: ' + data.error, 'error');
            } else {
                if (outputResult) outputResult.value = data.output;
                showNotification('Конвертация завершена', 'success');
            }
        } catch (error) {
            showNotification('Ошибка: ' + error.message, 'error');
        } finally {
            btnConvert.disabled = false;
            btnConvert.textContent = 'Конвертировать';
        }
    });

    btnCopy?.addEventListener('click', () => {
        outputResult?.select();
        document.execCommand('copy');
        if (btnCopy) {
            btnCopy.textContent = 'Скопировано!';
            setTimeout(() => btnCopy.textContent = 'Скопировать', 2000);
        }
    });

    // ============ FLICKR GALLERY (Albums Grid + Photos View) ============

    // DOM Elements
    const albumsView = document.getElementById('albums-view');
    const photosView = document.getElementById('photos-view');
    const albumsGrid = document.getElementById('albums-grid');
    const btnLoadAlbums = document.getElementById('btn-load-albums');
    const searchAlbums = document.getElementById('search-albums');
    const searchPhotos = document.getElementById('search-photos');
    const btnBackToAlbums = document.getElementById('btn-back-to-albums');
    const currentAlbumTitle = document.getElementById('current-album-title');
    const photosCountEl = document.getElementById('photos-count');
    const btnPrevPage = document.getElementById('btn-prev-page');
    const btnNextPage = document.getElementById('btn-next-page');
    const pageInfo = document.getElementById('page-info');
    const dragHint = document.getElementById('drag-hint');

    // Drag state
    let draggedAlbum = null;
    let draggedElement = null;

    // ============ ALBUMS GRID ============

    function renderAlbumsGrid(albums, filterText = '') {
        if (!albumsGrid) return;

        // Apply order from preferences
        const prefs = getAlbumPrefs();
        let orderedAlbums = [...albums];

        // Sort by saved order if available
        if (prefs.order && prefs.order.length > 0) {
            const orderMap = new Map(prefs.order.map((id, idx) => [id, idx]));
            orderedAlbums.sort((a, b) => {
                const aIdx = orderMap.has(a.id) ? orderMap.get(a.id) : 9999;
                const bIdx = orderMap.has(b.id) ? orderMap.get(b.id) : 9999;
                return aIdx - bIdx;
            });
        }

        // Filter by search text
        if (filterText) {
            const lower = filterText.toLowerCase();
            orderedAlbums = orderedAlbums.filter(a => {
                const title = (a.title?._content || a.title || '').toLowerCase();
                return title.includes(lower);
            });
        }

        if (orderedAlbums.length === 0) {
            albumsGrid.innerHTML = '<p class="placeholder">Альбомы не найдены</p>';
            if (dragHint) dragHint.classList.add('hidden');
            return;
        }

        if (dragHint) dragHint.classList.remove('hidden');

        albumsGrid.innerHTML = '';
        orderedAlbums.forEach(album => {
            const card = createAlbumCard(album);
            albumsGrid.appendChild(card);
        });
    }

    function createAlbumCard(album) {
        const card = document.createElement('div');
        card.className = 'album-card';
        card.dataset.albumId = album.id;
        card.draggable = true;

        const title = album.title?._content || album.title || 'Без названия';
        const count = album.photos || 0;
        const coverUrl = album.primary_photo_extras?.url_m ||
                        album.primary_photo_extras?.url_s ||
                        album.primary_photo_extras?.url_sq ||
                        null;

        card.innerHTML = `
            ${coverUrl
                ? `<img class="album-card-cover" src="${coverUrl}" alt="${escapeHtml(title)}" loading="lazy">`
                : `<div class="album-card-placeholder">📁</div>`
            }
            <div class="album-card-overlay">
                <div class="album-card-title">${escapeHtml(title)}</div>
                <div class="album-card-count">${count} фото</div>
            </div>
            <div class="album-card-drag-handle" title="Перетащите для изменения порядка">⋮⋮</div>
        `;

        // Click to open album
        card.addEventListener('click', (e) => {
            if (e.target.closest('.album-card-drag-handle')) return;
            openAlbum(album);
        });

        // Drag events
        card.addEventListener('dragstart', handleDragStart);
        card.addEventListener('dragend', handleDragEnd);
        card.addEventListener('dragover', handleDragOver);
        card.addEventListener('dragenter', handleDragEnter);
        card.addEventListener('dragleave', handleDragLeave);
        card.addEventListener('drop', handleDrop);

        return card;
    }

    // ============ DRAG AND DROP ============

    function handleDragStart(e) {
        draggedElement = e.currentTarget;
        draggedAlbum = e.currentTarget.dataset.albumId;
        e.currentTarget.classList.add('dragging');
        e.dataTransfer.effectAllowed = 'move';
        e.dataTransfer.setData('text/plain', draggedAlbum);
    }

    function handleDragEnd(e) {
        e.currentTarget.classList.remove('dragging');
        document.querySelectorAll('.album-card.drag-over').forEach(el => {
            el.classList.remove('drag-over');
        });
        draggedElement = null;
        draggedAlbum = null;
    }

    function handleDragOver(e) {
        e.preventDefault();
        e.dataTransfer.dropEffect = 'move';
    }

    function handleDragEnter(e) {
        e.preventDefault();
        if (e.currentTarget !== draggedElement) {
            e.currentTarget.classList.add('drag-over');
        }
    }

    function handleDragLeave(e) {
        e.currentTarget.classList.remove('drag-over');
    }

    function handleDrop(e) {
        e.preventDefault();
        const targetCard = e.currentTarget;
        targetCard.classList.remove('drag-over');

        if (!draggedElement || targetCard === draggedElement) return;

        const targetId = targetCard.dataset.albumId;
        const sourceId = draggedAlbum;

        // Get current order from DOM
        const cards = Array.from(albumsGrid.querySelectorAll('.album-card'));
        const currentOrder = cards.map(c => c.dataset.albumId);

        const sourceIdx = currentOrder.indexOf(sourceId);
        const targetIdx = currentOrder.indexOf(targetId);

        if (sourceIdx === -1 || targetIdx === -1) return;

        // Move element in DOM
        if (sourceIdx < targetIdx) {
            targetCard.parentNode.insertBefore(draggedElement, targetCard.nextSibling);
        } else {
            targetCard.parentNode.insertBefore(draggedElement, targetCard);
        }

        // Save new order
        const newOrder = Array.from(albumsGrid.querySelectorAll('.album-card'))
            .map(c => c.dataset.albumId);
        saveAlbumOrder(newOrder);

        showNotification('Порядок альбомов сохранён', 'success');
    }

    function saveAlbumOrder(order) {
        const prefs = getAlbumPrefs();
        prefs.order = order;
        saveAlbumPrefs(prefs);
    }

    // ============ ALBUM NAVIGATION ============

    function openAlbum(album) {
        state.currentAlbum = album.id;
        state.currentPage = 1;

        // Update UI
        if (currentAlbumTitle) {
            currentAlbumTitle.textContent = album.title?._content || album.title || 'Альбом';
        }

        // Switch views
        albumsView?.classList.add('hidden');
        photosView?.classList.remove('hidden');

        // Load photos
        loadPhotos();
    }

    function closeAlbum() {
        state.currentAlbum = '';
        state.currentPage = 1;

        // Switch views
        photosView?.classList.add('hidden');
        albumsView?.classList.remove('hidden');

        // Clear search
        if (searchPhotos) searchPhotos.value = '';

        // Silently refresh albums in background
        refreshAlbumsSilently();
    }

    // Refresh albums in background without showing loading to user
    async function refreshAlbumsSilently() {
        if (state.isLoadingAlbums) return;

        try {
            const response = await fetch('api.php?action=flickr_albums');
            if (!response.ok) return;

            const data = await response.json();
            if (data.albums && data.albums.length > 0) {
                setAlbumCache(data.albums);
                window._cachedAlbums = data.albums;
                // Only re-render if on albums view (photos view is hidden)
                if (photosView?.classList.contains('hidden')) {
                    renderAlbumsGrid(data.albums);
                }
            }
        } catch (error) {
            console.log('Silent refresh failed:', error.message);
        }
    }

    // ============ LOAD ALBUMS (with infinite scroll) ============

    async function loadAlbums(forceRefresh = false) {
        console.log('loadAlbums called, forceRefresh:', forceRefresh);

        // Check if albumsGrid exists
        if (!albumsGrid) {
            console.error('albumsGrid element not found!');
            showNotification('Ошибка: элемент галереи не найден', 'error');
            return;
        }

        if (state.isLoadingAlbums) {
            console.log('Albums already loading, skipping...');
            showNotification('Альбомы уже загружаются...', 'info');
            return;
        }

        // Reset pagination state
        state.albumsPage = 1;
        state.albumsTotalPages = 1;
        state.allAlbums = [];

        // Check cache first
        if (!forceRefresh) {
            const cached = getAlbumCache();
            if (cached && cached.albums) {
                console.log('Using cached albums:', cached.albums.length);
                state.allAlbums = cached.albums;
                state.albumsPage = cached.page || 1;
                state.albumsTotalPages = cached.pages || 1;
                state.albumsTotal = cached.total || cached.albums.length;
                window._cachedAlbums = cached.albums;
                renderAlbumsGrid(cached.albums);
                showNotification(`Загружено ${cached.albums.length} альбомов (из кеша)`, 'success');
                setupAlbumsInfiniteScroll();
                return;
            } else if (cached && Array.isArray(cached)) {
                // Old cache format compatibility
                console.log('Using old cached albums:', cached.length);
                state.allAlbums = cached;
                window._cachedAlbums = cached;
                renderAlbumsGrid(cached);
                showNotification(`Загружено ${cached.length} альбомов (из кеша)`, 'success');
                return;
            }
        }

        // Cancel previous request
        if (albumAbortController) {
            albumAbortController.abort();
        }
        albumAbortController = new AbortController();

        state.isLoadingAlbums = true;
        state.albumRequestId++;
        const thisRequestId = state.albumRequestId;

        // Show loading state
        albumsGrid.innerHTML = `
            <div class="loading" style="grid-column: 1/-1;">
                <div class="loading-spinner"></div>
                <span>Загрузка альбомов с Flickr...</span>
            </div>
        `;
        if (dragHint) dragHint.classList.add('hidden');

        // Also update button state
        if (btnLoadAlbums) {
            btnLoadAlbums.disabled = true;
            btnLoadAlbums.innerHTML = '<span class="btn-icon-text">⏳</span> Загрузка...';
        }

        try {
            console.log('Fetching albums from API...');

            // Add timeout
            const timeoutId = setTimeout(() => {
                albumAbortController.abort();
                console.error('Request timed out after 30 seconds');
            }, 30000);

            const response = await fetch(`api.php?action=flickr_albums&page=1&per_page=50`, {
                signal: albumAbortController.signal
            });

            clearTimeout(timeoutId);

            console.log('API response status:', response.status);

            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }

            if (thisRequestId !== state.albumRequestId) {
                console.log('Album request superseded');
                return;
            }

            const data = await response.json();
            console.log('API response data:', data);

            if (data.error) {
                throw new Error(data.error);
            }

            if (data.albums && data.albums.length > 0) {
                state.allAlbums = data.albums;
                state.albumsPage = data.page || 1;
                state.albumsTotalPages = data.pages || 1;
                state.albumsTotal = data.total || data.albums.length;

                // Save to cache with pagination info
                setAlbumCache({
                    albums: data.albums,
                    page: state.albumsPage,
                    pages: state.albumsTotalPages,
                    total: state.albumsTotal
                });
                window._cachedAlbums = data.albums;
                renderAlbumsGrid(data.albums);

                const hasMore = state.albumsPage < state.albumsTotalPages;
                const totalInfo = state.albumsTotal > data.albums.length
                    ? ` (${data.albums.length} из ${state.albumsTotal})`
                    : '';
                showNotification(`Загружено ${data.albums.length} альбомов${totalInfo}`, 'success');

                // Setup infinite scroll if there are more pages
                if (hasMore) {
                    setupAlbumsInfiniteScroll();
                }
            } else {
                albumsGrid.innerHTML = '<p class="placeholder">Альбомы не найдены. Проверьте настройки Flickr API.</p>';
                showNotification('Альбомы не найдены', 'error');
            }
        } catch (error) {
            if (error.name === 'AbortError') {
                console.log('Request aborted');
                albumsGrid.innerHTML = `
                    <div class="error-message" style="grid-column: 1/-1; text-align: center; padding: 40px;">
                        <p style="font-size: 2rem; margin-bottom: 10px;">⏱️</p>
                        <p class="placeholder">Превышено время ожидания. Flickr API не отвечает.</p>
                        <button class="btn btn-primary" style="margin-top: 15px;" onclick="location.reload()">Попробовать снова</button>
                    </div>
                `;
                showNotification('Таймаут: сервер не отвечает', 'error');
                return;
            }
            console.error('Ошибка загрузки альбомов:', error);
            albumsGrid.innerHTML = `
                <div class="error-message" style="grid-column: 1/-1; text-align: center; padding: 40px;">
                    <p style="font-size: 2rem; margin-bottom: 10px;">⚠️</p>
                    <p class="placeholder">Ошибка загрузки: ${escapeHtml(error.message)}</p>
                    <button onclick="location.reload()" class="btn btn-small" style="margin-top: 15px;">Перезагрузить страницу</button>
                </div>
            `;
            showNotification('Ошибка: ' + error.message, 'error');
        } finally {
            state.isLoadingAlbums = false;
            if (btnLoadAlbums) {
                btnLoadAlbums.disabled = false;
                btnLoadAlbums.innerHTML = '<span class="btn-icon-text">↻</span> Загрузить альбомы';
            }
        }
    }

    // Load more albums (infinite scroll)
    async function loadMoreAlbums() {
        if (state.isLoadingMoreAlbums || state.albumsPage >= state.albumsTotalPages) {
            return;
        }

        state.isLoadingMoreAlbums = true;
        const nextPage = state.albumsPage + 1;

        // Show loading indicator at bottom
        const loadingEl = document.createElement('div');
        loadingEl.className = 'albums-loading-more';
        loadingEl.innerHTML = `
            <div class="loading-spinner"></div>
            <span>Загрузка альбомов...</span>
        `;
        albumsGrid.appendChild(loadingEl);

        try {
            const response = await fetch(`api.php?action=flickr_albums&page=${nextPage}&per_page=50`);
            const data = await response.json();

            // Remove loading indicator
            loadingEl.remove();

            if (data.error) {
                throw new Error(data.error);
            }

            if (data.albums && data.albums.length > 0) {
                state.albumsPage = data.page;
                state.allAlbums = [...state.allAlbums, ...data.albums];
                window._cachedAlbums = state.allAlbums;

                // Update cache
                setAlbumCache({
                    albums: state.allAlbums,
                    page: state.albumsPage,
                    pages: state.albumsTotalPages,
                    total: state.albumsTotal
                });

                // Append new albums to grid
                appendAlbumsToGrid(data.albums);

                console.log(`Loaded page ${nextPage}/${state.albumsTotalPages}, total albums: ${state.allAlbums.length}`);
            }
        } catch (error) {
            loadingEl.remove();
            console.error('Error loading more albums:', error);
            showNotification('Ошибка загрузки альбомов: ' + error.message, 'error');
        } finally {
            state.isLoadingMoreAlbums = false;
            // IntersectionObserver fires on STATE CHANGE — if the sentinel never left the
            // viewport (e.g. small thumbnails) it won't auto-trigger again. Re-check here.
            if (state.albumsPage < state.albumsTotalPages) {
                requestAnimationFrame(() => {
                    const sentinel = document.getElementById('albums-scroll-sentinel');
                    if (!sentinel || state.isLoadingMoreAlbums) return;
                    const rect = sentinel.getBoundingClientRect();
                    const viewportH = window.innerHeight || document.documentElement.clientHeight;
                    if (rect.top < viewportH + 200) {
                        loadMoreAlbums();
                    }
                });
            }
        }
    }

    // Append albums to existing grid
    function appendAlbumsToGrid(albums) {
        albums.forEach(album => {
            const card = createAlbumCard(album);
            albumsGrid.appendChild(card);
        });

        if (dragHint && state.allAlbums.length > 1) {
            dragHint.classList.remove('hidden');
        }
    }

    // Setup infinite scroll observer for albums
    let albumsScrollObserver = null;

    function setupAlbumsInfiniteScroll() {
        // Remove existing observer
        if (albumsScrollObserver) {
            albumsScrollObserver.disconnect();
        }

        // Create sentinel element
        let sentinel = document.getElementById('albums-scroll-sentinel');
        if (!sentinel) {
            sentinel = document.createElement('div');
            sentinel.id = 'albums-scroll-sentinel';
            sentinel.style.cssText = 'height: 20px; grid-column: 1/-1;';
        }

        // Append sentinel after grid
        if (albumsGrid && albumsGrid.parentNode) {
            // Insert after albumsGrid
            albumsGrid.parentNode.insertBefore(sentinel, albumsGrid.nextSibling);
        }

        // Create intersection observer
        albumsScrollObserver = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting && !state.isLoadingMoreAlbums && state.albumsPage < state.albumsTotalPages) {
                    console.log('Albums sentinel visible, loading more...');
                    loadMoreAlbums();
                }
            });
        }, {
            root: null,
            rootMargin: '200px',
            threshold: 0
        });

        albumsScrollObserver.observe(sentinel);

        // Fallback scroll listener — covers cases where IntersectionObserver
        // doesn't refire after a load (e.g. sentinel stays in viewport).
        if (!window._albumsScrollFallbackBound) {
            window._albumsScrollFallbackBound = true;
            const checkSentinel = () => {
                if (state.isLoadingMoreAlbums) return;
                if (state.albumsPage >= state.albumsTotalPages) return;
                const s = document.getElementById('albums-scroll-sentinel');
                if (!s) return;
                const rect = s.getBoundingClientRect();
                const viewportH = window.innerHeight || document.documentElement.clientHeight;
                if (rect.top < viewportH + 200) {
                    loadMoreAlbums();
                }
            };
            window.addEventListener('scroll', checkSentinel, { passive: true });
            window.addEventListener('resize', checkSentinel, { passive: true });
        }
    }

    // ============ LOAD PHOTOS (with infinite scroll) ============

    async function loadPhotos() {
        if (!photoGallery) return;

        if (photoAbortController) {
            photoAbortController.abort();
        }
        photoAbortController = new AbortController();

        // Reset state for new load
        state.currentPage = 1;
        state.totalPages = 1;
        state.allPhotos = [];
        state.isLoadingPhotos = true;
        state.photoRequestId++;
        const thisRequestId = state.photoRequestId;

        photoGallery.innerHTML = `
            <div class="loading">
                <div class="loading-spinner"></div>
                <span>Загрузка фотографий...</span>
            </div>
        `;

        try {
            const params = new URLSearchParams({
                action: 'flickr_photos',
                page: 1,
                per_page: 50
            });

            if (state.currentAlbum) params.append('album_id', state.currentAlbum);
            if (searchPhotos?.value.trim()) params.append('search', searchPhotos.value.trim());

            const response = await fetch('api.php?' + params, {
                signal: photoAbortController.signal
            });

            if (thisRequestId !== state.photoRequestId) {
                console.log('Photo request superseded');
                return;
            }

            const data = await response.json();

            if (data.error) {
                photoGallery.innerHTML = `<p class="placeholder">Ошибка: ${data.error}</p>`;
                return;
            }

            state.totalPages = data.pagination?.pages || 1;
            state.currentPage = data.pagination?.page || 1;
            const totalPhotos = data.pagination?.total || 0;
            updatePagination();

            if (photosCountEl) {
                photosCountEl.textContent = `${totalPhotos} фото`;
            }

            if (!data.photos || data.photos.length === 0) {
                photoGallery.innerHTML = '<p class="placeholder">Фотографии не найдены</p>';
                return;
            }

            state.allPhotos = data.photos;
            photoGallery.innerHTML = '';
            renderPhotos(data.photos);

            // Setup infinite scroll if there are more pages
            if (state.currentPage < state.totalPages) {
                setupPhotosInfiniteScroll();
            }

        } catch (error) {
            if (error.name === 'AbortError') return;
            photoGallery.innerHTML = `<p class="placeholder">Ошибка: ${error.message}</p>`;
            showNotification('Ошибка загрузки фотографий', 'error');
        } finally {
            state.isLoadingPhotos = false;
        }
    }

    // Render photos to gallery
    function renderPhotos(photos, append = false) {
        if (!append) {
            photoGallery.innerHTML = '';
        }

        photos.forEach(photo => {
            const div = document.createElement('div');
            div.className = 'photo-item';
            if (photo.is_video) {
                div.classList.add('is-video');
            }
            div.dataset.photoId = photo.id;
            div.dataset.photoData = JSON.stringify(photo);

            if (state.selectedPhotos.find(p => p.id === photo.id)) {
                div.classList.add('selected');
            }

            const videoBadge = photo.is_video ? '<div class="video-badge"><span class="video-icon">▶</span></div>' : '';

            div.innerHTML = `
                <div class="checkbox"></div>
                ${videoBadge}
                <img src="${photo.urls.small || photo.urls.medium || photo.urls.square}" alt="${escapeHtml(photo.title)}" loading="lazy">
                <div class="title">${escapeHtml(photo.title)}</div>
                <button class="photo-preview-btn" title="Просмотр">${photo.is_video ? '▶' : '🔍'}</button>
            `;

            // Checkbox click = toggle selection
            div.querySelector('.checkbox').addEventListener('click', (e) => {
                e.stopPropagation();
                togglePhotoSelection(div, photo);
            });

            // Image click = open lightbox
            div.querySelector('img').addEventListener('click', (e) => {
                e.stopPropagation();
                openLightbox(photo);
            });

            // Preview button = open lightbox
            div.querySelector('.photo-preview-btn').addEventListener('click', (e) => {
                e.stopPropagation();
                openLightbox(photo);
            });

            // Background click = toggle selection
            div.addEventListener('click', () => togglePhotoSelection(div, photo));

            photoGallery.appendChild(div);
        });
    }

    // Load more photos (infinite scroll)
    async function loadMorePhotos() {
        if (state.isLoadingMorePhotos || state.currentPage >= state.totalPages) {
            return;
        }

        state.isLoadingMorePhotos = true;
        const nextPage = state.currentPage + 1;

        // Show loading indicator at bottom
        const loadingEl = document.createElement('div');
        loadingEl.className = 'photos-loading-more';
        loadingEl.innerHTML = `
            <div class="loading-spinner"></div>
            <span>Загрузка фотографий...</span>
        `;
        photoGallery.appendChild(loadingEl);

        try {
            const params = new URLSearchParams({
                action: 'flickr_photos',
                page: nextPage,
                per_page: 50
            });

            if (state.currentAlbum) params.append('album_id', state.currentAlbum);
            if (searchPhotos?.value.trim()) params.append('search', searchPhotos.value.trim());

            const response = await fetch('api.php?' + params);
            const data = await response.json();

            // Remove loading indicator
            loadingEl.remove();

            if (data.error) {
                throw new Error(data.error);
            }

            if (data.photos && data.photos.length > 0) {
                state.currentPage = data.pagination?.page || nextPage;
                state.allPhotos = [...state.allPhotos, ...data.photos];

                // Append new photos
                renderPhotos(data.photos, true);
                updatePagination();

                console.log(`Loaded photos page ${nextPage}/${state.totalPages}, total: ${state.allPhotos.length}`);
            }
        } catch (error) {
            loadingEl.remove();
            console.error('Error loading more photos:', error);
            showNotification('Ошибка загрузки фотографий: ' + error.message, 'error');
        } finally {
            state.isLoadingMorePhotos = false;
        }
    }

    // Setup infinite scroll observer for photos
    let photosScrollObserver = null;

    function setupPhotosInfiniteScroll() {
        // Remove existing observer
        if (photosScrollObserver) {
            photosScrollObserver.disconnect();
        }

        // Create sentinel element
        let sentinel = document.getElementById('photos-scroll-sentinel');
        if (!sentinel) {
            sentinel = document.createElement('div');
            sentinel.id = 'photos-scroll-sentinel';
            sentinel.style.cssText = 'height: 20px; width: 100%; clear: both;';
        }

        // Append sentinel after gallery
        if (photoGallery && photoGallery.parentNode) {
            photoGallery.parentNode.insertBefore(sentinel, photoGallery.nextSibling);
        }

        // Create intersection observer
        photosScrollObserver = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting && !state.isLoadingMorePhotos && state.currentPage < state.totalPages) {
                    console.log('Photos sentinel visible, loading more...');
                    loadMorePhotos();
                }
            });
        }, {
            root: null,
            rootMargin: '300px',
            threshold: 0
        });

        photosScrollObserver.observe(sentinel);
    }

    function togglePhotoSelection(element, photo) {
        const index = state.selectedPhotos.findIndex(p => p.id === photo.id);

        if (index === -1) {
            if (!canAddPhotos(1)) return;
            state.selectedPhotos.push(photo);
            element.classList.add('selected');
        } else {
            state.selectedPhotos.splice(index, 1);
            element.classList.remove('selected');
        }

        updateSelectionUI();
        saveSelectedPhotos(); // Save to localStorage
    }

    function updatePagination() {
        // Show loaded/total info for infinite scroll
        const loaded = state.allPhotos.length;
        const total = state.totalPages * 50; // Approximate total
        if (pageInfo) {
            if (state.currentPage < state.totalPages) {
                pageInfo.textContent = `Загружено: ${loaded}`;
            } else {
                pageInfo.textContent = `Все фото загружены`;
            }
        }
        // Hide old pagination buttons (infinite scroll replaces them)
        if (btnPrevPage) btnPrevPage.style.display = 'none';
        if (btnNextPage) btnNextPage.style.display = 'none';
    }

    // ============ EVENT LISTENERS ============

    // Load albums button
    if (btnLoadAlbums) {
        console.log('btnLoadAlbums found, attaching click listener');
        btnLoadAlbums.addEventListener('click', () => {
            console.log('Load albums button clicked!');
            loadAlbums(true);
        });
    } else {
        console.error('btnLoadAlbums element not found! Check ID: btn-load-albums');
    }

    // Back to albums
    btnBackToAlbums?.addEventListener('click', closeAlbum);

    // Search albums
    let albumSearchTimeout;
    searchAlbums?.addEventListener('input', () => {
        clearTimeout(albumSearchTimeout);
        albumSearchTimeout = setTimeout(() => {
            if (window._cachedAlbums) {
                renderAlbumsGrid(window._cachedAlbums, searchAlbums.value.trim());
            }
        }, 300);
    });

    // Search photos
    let photoSearchTimeout;
    searchPhotos?.addEventListener('input', () => {
        clearTimeout(photoSearchTimeout);
        photoSearchTimeout = setTimeout(() => {
            state.currentPage = 1;
            loadPhotos();
        }, 500);
    });

    // Pagination
    btnPrevPage?.addEventListener('click', () => {
        if (state.currentPage > 1) {
            state.currentPage--;
            loadPhotos();
        }
    });

    btnNextPage?.addEventListener('click', () => {
        if (state.currentPage < state.totalPages) {
            state.currentPage++;
            loadPhotos();
        }
    });

    // ============ FLOATING ACTION BAR ============

    document.getElementById('btn-select-all')?.addEventListener('click', () => {
        const maxNow = getMaxPhotos();
        document.querySelectorAll('.photo-item').forEach(item => {
            if (getTotalPhotosCount() >= maxNow) return;
            const photo = JSON.parse(item.dataset.photoData);
            if (!state.selectedPhotos.find(p => p.id === photo.id)) {
                state.selectedPhotos.push(photo);
                item.classList.add('selected');
            }
        });
        if (getTotalPhotosCount() >= maxNow) {
            showNotification(`Выбрано максимум ${maxNow} фото`, 'info');
        }
        updateSelectionUI();
        saveSelectedPhotos();
    });

    document.getElementById('btn-deselect-all')?.addEventListener('click', () => {
        state.selectedPhotos = [];
        document.querySelectorAll('.photo-item').forEach(item => item.classList.remove('selected'));
        updateSelectionUI();
        saveSelectedPhotos();
    });

    document.getElementById('btn-convert-selected')?.addEventListener('click', () => {
        if (state.selectedPhotos.length === 0) {
            showNotification('Сначала выберите фотографии', 'error');
            return;
        }
        const urls = state.selectedPhotos.map(p => p.page_url).join('\n');
        if (inputUrls) inputUrls.value = urls;
        document.querySelector('.nav-btn[data-tab="converter"]')?.click();
    });

    document.getElementById('btn-telegram-selected')?.addEventListener('click', () => {
        if (state.selectedPhotos.length === 0) {
            showNotification('Сначала выберите фотографии', 'error');
            return;
        }
        document.querySelector('.nav-btn[data-tab="posting"]')?.click();
    });

    // Get best quality URL for photo (prefer original, but only use actual URLs)
    function getBestPhotoUrl(photo) {
        // Priority: original > large2048 > large > medium640 > medium
        // Only return URLs that actually exist (not null/undefined)
        const urls = photo.urls || {};
        return urls.original || urls.large2048 || urls.large || urls.medium640 || urls.medium;
    }

    // Get preview URL (medium size for lightbox)
    function getPreviewUrl(photo) {
        return photo.urls.large || photo.urls.medium640 || photo.urls.medium || photo.urls.original;
    }

    // Get file extension from photo
    function getPhotoExtension(photo) {
        return photo.original_format || 'jpg';
    }

    // ============ SERVER-SIDE DOWNLOAD (bypasses CORS) ============

    // Download single photo via server proxy
    function downloadSinglePhoto(photo) {
        const url = getBestPhotoUrl(photo);
        const ext = getPhotoExtension(photo);
        const title = (photo.title || photo.id).replace(/[<>:"/\\|?*]/g, '_').substring(0, 100);

        // Create download URL through our server proxy
        const downloadUrl = `download.php?action=photo&url=${encodeURIComponent(url)}&filename=${encodeURIComponent(title)}&format=${ext}`;

        // Trigger download via hidden iframe or link
        const link = document.createElement('a');
        link.href = downloadUrl;
        link.download = `${title}.${ext}`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }

    // Download multiple photos as individual files (sequential)
    async function downloadPhotosIndividually(photos) {
        showNotification(`Скачивание ${photos.length} фото...`, 'info');

        for (let i = 0; i < photos.length; i++) {
            downloadSinglePhoto(photos[i]);
            // Small delay between downloads to avoid overwhelming browser
            if (i < photos.length - 1) {
                await new Promise(resolve => setTimeout(resolve, 500));
            }
        }

        showNotification(`✓ Загрузка ${photos.length} фото начата`, 'success');
    }

    // Download multiple photos as ZIP archive
    function downloadPhotosAsZip(photos, albumName = '') {
        showNotification(`Подготовка архива: ${photos.length} фото...`, 'info');

        // Prepare photo data for server
        const photoData = photos.map(photo => ({
            id: photo.id,
            url: getBestPhotoUrl(photo),
            title: photo.title || photo.id,
            format: getPhotoExtension(photo)
        }));

        try {
            // Create form for POST request
            const form = document.createElement('form');
            form.method = 'POST';
            form.action = 'download.php?action=zip';
            form.style.display = 'none';

            const photosInput = document.createElement('input');
            photosInput.type = 'hidden';
            photosInput.name = 'photos';
            photosInput.value = JSON.stringify(photoData);
            form.appendChild(photosInput);

            const albumInput = document.createElement('input');
            albumInput.type = 'hidden';
            albumInput.name = 'album_name';
            albumInput.value = albumName;
            form.appendChild(albumInput);

            document.body.appendChild(form);
            form.submit();
            document.body.removeChild(form);

            showNotification('✓ Создание архива на сервере...', 'success');
        } catch (error) {
            console.error('Failed to create ZIP:', error);
            showNotification('Ошибка создания архива', 'error');
        }
    }

    // Show download format choice dialog
    function showDownloadChoiceDialog(photos, albumName = '') {
        // Remove existing dialog if any
        const existing = document.getElementById('download-choice-dialog');
        if (existing) existing.remove();

        const dialog = document.createElement('div');
        dialog.id = 'download-choice-dialog';
        dialog.className = 'download-dialog-overlay';
        dialog.innerHTML = `
            <div class="download-dialog">
                <h3>Скачать ${photos.length} фото</h3>
                <p>Выберите формат загрузки:</p>
                <div class="download-dialog-buttons">
                    <button class="btn btn-primary" data-action="zip">
                        <span class="btn-icon">📦</span>
                        ZIP архив
                    </button>
                    <button class="btn btn-secondary" data-action="individual">
                        <span class="btn-icon">📄</span>
                        Отдельные файлы
                    </button>
                </div>
                <button class="download-dialog-close" title="Отмена">×</button>
            </div>
        `;

        dialog.querySelector('[data-action="zip"]').addEventListener('click', () => {
            dialog.remove();
            downloadPhotosAsZip(photos, albumName);
        });

        dialog.querySelector('[data-action="individual"]').addEventListener('click', () => {
            dialog.remove();
            downloadPhotosIndividually(photos);
        });

        dialog.querySelector('.download-dialog-close').addEventListener('click', () => {
            dialog.remove();
        });

        dialog.querySelector('.download-dialog-overlay')?.addEventListener('click', (e) => {
            if (e.target === dialog) dialog.remove();
        });

        // Close on backdrop click
        dialog.addEventListener('click', (e) => {
            if (e.target === dialog) dialog.remove();
        });

        document.body.appendChild(dialog);
    }

    // Download photos - single directly, multiple with choice
    async function downloadPhotos(photos, albumName = '') {
        if (photos.length === 0) {
            showNotification('Нет фото для скачивания', 'error');
            return;
        }

        // Single photo - download directly via server proxy
        if (photos.length === 1) {
            showNotification('Скачивание фото...', 'info');
            downloadSinglePhoto(photos[0]);
            showNotification('✓ Загрузка начата', 'success');
            return;
        }

        // Multiple photos - show choice dialog
        showDownloadChoiceDialog(photos, albumName);
    }

    // ============ PHOTO LIGHTBOX (Preview) ============

    let lightboxPhoto = null;

    function createLightbox() {
        // Create lightbox container if not exists
        if (document.getElementById('photo-lightbox')) return;

        const lightbox = document.createElement('div');
        lightbox.id = 'photo-lightbox';
        lightbox.className = 'lightbox hidden';
        lightbox.innerHTML = `
            <div class="lightbox-backdrop"></div>
            <div class="lightbox-content">
                <button class="lightbox-close" title="Закрыть">×</button>
                <div class="lightbox-image-container">
                    <img class="lightbox-image" src="" alt="">
                    <div class="lightbox-video-container" style="display: none;">
                        <iframe class="lightbox-video" src="" frameborder="0" allowfullscreen allow="autoplay"></iframe>
                    </div>
                    <div class="lightbox-loading">
                        <div class="loading-spinner"></div>
                    </div>
                </div>
                <div class="lightbox-info">
                    <h3 class="lightbox-title"></h3>
                </div>
                <div class="lightbox-actions">
                    <button class="btn btn-primary lightbox-btn-select">
                        <span class="btn-icon">✓</span> Выбрать
                    </button>
                    <button class="btn btn-secondary lightbox-btn-download">
                        <span class="btn-icon">↓</span> Скачать
                    </button>
                    <a class="btn btn-secondary lightbox-btn-flickr" href="#" target="_blank" title="Открыть на Flickr">
                        <span class="btn-icon">🔗</span> Flickr
                    </a>
                </div>
                <button class="lightbox-nav lightbox-prev" title="Предыдущее">‹</button>
                <button class="lightbox-nav lightbox-next" title="Следующее">›</button>
            </div>
        `;

        document.body.appendChild(lightbox);

        // Event listeners
        lightbox.querySelector('.lightbox-backdrop').addEventListener('click', closeLightbox);
        lightbox.querySelector('.lightbox-close').addEventListener('click', closeLightbox);
        lightbox.querySelector('.lightbox-btn-select').addEventListener('click', lightboxSelectPhoto);
        lightbox.querySelector('.lightbox-btn-download').addEventListener('click', lightboxDownloadPhoto);
        lightbox.querySelector('.lightbox-prev').addEventListener('click', lightboxPrevPhoto);
        lightbox.querySelector('.lightbox-next').addEventListener('click', lightboxNextPhoto);

        // Keyboard navigation
        document.addEventListener('keydown', handleLightboxKeyboard);
    }

    function openLightbox(photo) {
        createLightbox();
        lightboxPhoto = photo;

        const lightbox = document.getElementById('photo-lightbox');
        const img = lightbox.querySelector('.lightbox-image');
        const videoContainer = lightbox.querySelector('.lightbox-video-container');
        const video = lightbox.querySelector('.lightbox-video');
        const title = lightbox.querySelector('.lightbox-title');
        const loading = lightbox.querySelector('.lightbox-loading');
        const selectBtn = lightbox.querySelector('.lightbox-btn-select');
        const flickrBtn = lightbox.querySelector('.lightbox-btn-flickr');
        const downloadBtn = lightbox.querySelector('.lightbox-btn-download');

        const isVideo = photo.is_video;

        // Show loading
        loading.classList.remove('hidden');

        // Update Flickr link
        if (flickrBtn) {
            flickrBtn.href = photo.page_url || '#';
        }

        // Hide video container (not used due to Flickr restrictions)
        videoContainer.style.display = 'none';
        video.src = '';

        // Show image (for both photos and video thumbnails)
        img.style.display = 'block';
        img.style.opacity = '0';
        img.src = getPreviewUrl(photo);
        img.alt = photo.title || '';

        // Handle video play overlay
        let playOverlay = lightbox.querySelector('.video-play-overlay');
        if (!playOverlay) {
            playOverlay = document.createElement('div');
            playOverlay.className = 'video-play-overlay';
            playOverlay.innerHTML = `
                <div class="video-play-button">▶</div>
                <div class="video-play-text">Смотреть на Flickr</div>
            `;
            lightbox.querySelector('.lightbox-image-container').appendChild(playOverlay);
        }

        if (isVideo) {
            // Show play overlay for videos
            playOverlay.style.display = 'flex';
            playOverlay.onclick = () => window.open(photo.page_url, '_blank');
            if (downloadBtn) downloadBtn.style.display = 'none';
        } else {
            // Hide play overlay for photos
            playOverlay.style.display = 'none';
            if (downloadBtn) downloadBtn.style.display = '';
        }

        // When image loads
        img.onload = () => {
            loading.classList.add('hidden');
            img.style.opacity = '1';
        };

        img.onerror = () => {
            loading.classList.add('hidden');
            img.style.opacity = '1';
        };

        title.textContent = (isVideo ? '▶ ' : '') + (photo.title || 'Без названия');

        // Update select button state
        const isSelected = state.selectedPhotos.find(p => p.id === photo.id);
        selectBtn.innerHTML = isSelected
            ? '<span class="btn-icon">✓</span> Выбрано'
            : '<span class="btn-icon">+</span> Выбрать';
        selectBtn.className = isSelected ? 'btn btn-success lightbox-btn-select' : 'btn btn-primary lightbox-btn-select';

        // Show lightbox
        lightbox.classList.remove('hidden');
        document.body.style.overflow = 'hidden';

        updateLightboxNavigation();
    }

    function closeLightbox() {
        const lightbox = document.getElementById('photo-lightbox');
        if (lightbox) {
            // Stop video if playing
            const video = lightbox.querySelector('.lightbox-video');
            if (video) video.src = '';

            lightbox.classList.add('hidden');
            document.body.style.overflow = '';
            lightboxPhoto = null;
        }
    }

    function lightboxSelectPhoto() {
        if (!lightboxPhoto) return;

        const index = state.selectedPhotos.findIndex(p => p.id === lightboxPhoto.id);

        if (index === -1) {
            if (!canAddPhotos(1)) return;
            state.selectedPhotos.push(lightboxPhoto);
        } else {
            state.selectedPhotos.splice(index, 1);
        }

        // Update button
        const selectBtn = document.querySelector('.lightbox-btn-select');
        const isSelected = state.selectedPhotos.find(p => p.id === lightboxPhoto.id);
        selectBtn.innerHTML = isSelected
            ? '<span class="btn-icon">✓</span> Выбрано'
            : '<span class="btn-icon">+</span> Выбрать';
        selectBtn.className = isSelected ? 'btn btn-success lightbox-btn-select' : 'btn btn-primary lightbox-btn-select';

        // Update gallery
        updateSelectionUI();
        syncGallerySelection();
        saveSelectedPhotos();

        showNotification(isSelected ? 'Фото добавлено' : 'Фото удалено', 'info');
    }

    function lightboxDownloadPhoto() {
        if (!lightboxPhoto) return;
        showNotification('Скачивание...', 'info');
        downloadSinglePhoto(lightboxPhoto);
    }

    function getPhotosList() {
        return Array.from(document.querySelectorAll('.photo-item')).map(item => {
            try {
                return JSON.parse(item.dataset.photoData);
            } catch (e) {
                return null;
            }
        }).filter(p => p !== null);
    }

    function lightboxPrevPhoto() {
        if (!lightboxPhoto) return;
        const photos = getPhotosList();
        const currentIdx = photos.findIndex(p => p.id === lightboxPhoto.id);
        if (currentIdx > 0) {
            openLightbox(photos[currentIdx - 1]);
        }
    }

    function lightboxNextPhoto() {
        if (!lightboxPhoto) return;
        const photos = getPhotosList();
        const currentIdx = photos.findIndex(p => p.id === lightboxPhoto.id);
        if (currentIdx < photos.length - 1) {
            openLightbox(photos[currentIdx + 1]);
        }
    }

    function updateLightboxNavigation() {
        const lightbox = document.getElementById('photo-lightbox');
        if (!lightbox || !lightboxPhoto) return;

        const photos = getPhotosList();
        const currentIdx = photos.findIndex(p => p.id === lightboxPhoto.id);

        const prevBtn = lightbox.querySelector('.lightbox-prev');
        const nextBtn = lightbox.querySelector('.lightbox-next');

        prevBtn.style.visibility = currentIdx > 0 ? 'visible' : 'hidden';
        nextBtn.style.visibility = currentIdx < photos.length - 1 ? 'visible' : 'hidden';
    }

    function handleLightboxKeyboard(e) {
        const lightbox = document.getElementById('photo-lightbox');
        if (!lightbox || lightbox.classList.contains('hidden')) return;

        switch (e.key) {
            case 'Escape':
                closeLightbox();
                break;
            case 'ArrowLeft':
                lightboxPrevPhoto();
                break;
            case 'ArrowRight':
                lightboxNextPhoto();
                break;
            case ' ':
                e.preventDefault();
                lightboxSelectPhoto();
                break;
        }
    }

    // Download selected photos
    document.getElementById('btn-download-selected')?.addEventListener('click', () => {
        if (state.selectedPhotos.length === 0) {
            showNotification('Сначала выберите фотографии', 'error');
            return;
        }
        downloadPhotos(state.selectedPhotos);
    });

    // Download all photos in current album
    window.downloadAllPhotos = async function() {
        if (!state.currentAlbum) {
            showNotification('Сначала откройте альбом', 'error');
            return;
        }

        // Get album name for ZIP filename
        const albumName = currentAlbumTitle?.textContent?.replace(/[<>:"/\\|?*]/g, '_') || 'album';

        showNotification('Получение списка фото альбома...', 'info');

        try {
            // Fetch all photos from the album (increase per_page for full album)
            const params = new URLSearchParams({
                action: 'flickr_photos',
                album_id: state.currentAlbum,
                page: 1,
                per_page: 500 // Get more photos at once
            });

            const response = await fetch('api.php?' + params);
            const data = await response.json();

            if (data.error) {
                showNotification('Ошибка: ' + data.error, 'error');
                return;
            }

            if (!data.photos || data.photos.length === 0) {
                showNotification('Альбом пуст', 'error');
                return;
            }

            // Download all photos as ZIP with album name
            downloadPhotos(data.photos, albumName);
        } catch (error) {
            showNotification('Ошибка: ' + error.message, 'error');
        }
    };

    // ============ MULTI-PLATFORM POSTING ============

    async function loadTelegramStatus() {
        const statusEl = document.getElementById('tg-bot-status');
        const statusMini = document.getElementById('tg-status-mini');
        const tgChannel = document.getElementById('tg-channel');

        try {
            const response = await fetch('api.php?action=telegram_status');
            const data = await response.json();

            const connected = data.connected;
            const text = connected ? `@${data.bot_username}` : (data.message || 'Не подключён');

            if (statusEl) {
                statusEl.className = `status ${connected ? 'connected' : 'disconnected'}`;
                statusEl.textContent = connected ? `Подключён: ${text}` : text;
            }
            if (statusMini) {
                statusMini.className = `status-mini ${connected ? 'connected' : ''}`;
                statusMini.textContent = connected ? 'Подключён' : 'Не подключён';
            }
        } catch (error) {
            if (statusEl) {
                statusEl.className = 'status disconnected';
                statusEl.textContent = 'Ошибка проверки';
            }
        }

        // Load channels
        try {
            const response = await fetch('api.php?action=telegram_channels');
            const data = await response.json();

            if (data.channels && tgChannel) {
                tgChannel.innerHTML = '<option value="">Выберите канал...</option>';
                data.channels.forEach(channel => {
                    const option = document.createElement('option');
                    option.value = channel.id || channel;
                    option.textContent = channel.name || channel;
                    tgChannel.appendChild(option);
                });
                // Auto-select if only one channel
                if (data.channels.length === 1) {
                    tgChannel.selectedIndex = 1;
                }
            }
        } catch (error) {
            console.error('Ошибка загрузки каналов:', error);
        }
    }

    async function loadVKStatus() {
        const statusEl = document.getElementById('vk-status');
        const statusMini = document.getElementById('vk-status-mini');
        const vkGroup = document.getElementById('vk-group');

        try {
            const response = await fetch('api.php?action=vk_status');
            const data = await response.json();

            const connected = data.connected;
            const text = connected ? (data.user_name || 'VK') : (data.message || 'Не подключён');

            if (statusEl) {
                statusEl.className = `status ${connected ? 'connected' : 'disconnected'}`;
                statusEl.textContent = connected ? `Подключён: ${text}` : text;
            }
            if (statusMini) {
                statusMini.className = `status-mini ${connected ? 'connected' : ''}`;
                statusMini.textContent = connected ? 'Подключён' : 'Не подключён';
            }
        } catch (error) {
            if (statusEl) {
                statusEl.className = 'status disconnected';
                statusEl.textContent = 'Ошибка проверки';
            }
        }

        // Load groups
        try {
            const response = await fetch('api.php?action=vk_groups');
            const data = await response.json();

            if (data.groups && vkGroup) {
                vkGroup.innerHTML = '<option value="">Выберите группу...</option>';
                data.groups.forEach(group => {
                    const option = document.createElement('option');
                    option.value = group.id;
                    option.textContent = group.name;
                    vkGroup.appendChild(option);
                });
                // Auto-select if only one group (or community token)
                if (data.groups.length === 1) {
                    vkGroup.selectedIndex = 1;
                }
            }
        } catch (error) {
            console.error('Ошибка загрузки групп VK:', error);
        }
    }

    // Send post (immediate or scheduled)
    document.getElementById('btn-send-post')?.addEventListener('click', async () => {
        const postText = document.getElementById('post-text');
        let baseText = postText?.value || '';
        const parseMode = document.getElementById('post-parse-mode')?.value || 'HTML';
        const isScheduled = document.getElementById('chk-schedule')?.checked;
        const scheduledTime = document.getElementById('scheduled-datetime')?.value;

        // Check scheduled time
        if (isScheduled) {
            if (!scheduledTime) {
                showNotification('Укажите дату и время публикации', 'error');
                return;
            }
            const scheduledDate = new Date(scheduledTime);
            if (scheduledDate <= new Date()) {
                showNotification('Время публикации должно быть в будущем', 'error');
                return;
            }
        }

        // Add tags to text
        const tagsString = getTagsString('post');
        const currentTags = tagContexts?.post || [];
        if (tagsString) {
            baseText = baseText.trim() ? baseText.trim() + '\n\n' + tagsString : tagsString;
        }

        const platforms = [];
        const tgChannel = document.getElementById('tg-channel');
        const vkGroup = document.getElementById('vk-group');

        const postToTelegram = document.getElementById('chk-telegram')?.checked && tgChannel?.value;
        const postToVk = document.getElementById('chk-vk')?.checked && vkGroup?.value;

        if (postToTelegram) {
            platforms.push({ type: 'telegram', target: tgChannel.value });
        }
        if (postToVk) {
            platforms.push({ type: 'vk', target: vkGroup.value });
        }

        if (platforms.length === 0) {
            showNotification('Выберите платформу и канал/группу', 'error');
            return;
        }

        if (state.selectedPhotos.length === 0 && state.uploadedFiles.length === 0 && !baseText.trim()) {
            showNotification('Добавьте фото или текст', 'error');
            return;
        }

        const btnSendPost = document.getElementById('btn-send-post');
        const postResult = document.getElementById('post-result');

        // Handle scheduled posting
        if (isScheduled) {
            if (btnSendPost) {
                btnSendPost.disabled = true;
                btnSendPost.textContent = 'Планирование...';
            }

            try {
                const photoUrls = state.selectedPhotos.map(p => p.urls.large || p.urls.original || p.urls.medium640);
                const uploadedFileUrls = state.uploadedFiles
                    .filter(f => f.url && !f.uploading)
                    .map(f => ({ url: f.url, type: f.type }));

                const formData = new FormData();
                const isEditing = scheduledState.editingPostId !== null;
                formData.append('action', isEditing ? 'update_scheduled_post' : 'create_scheduled_post');
                if (isEditing) {
                    formData.append('id', scheduledState.editingPostId);
                }
                formData.append('text', postText?.value || '');
                formData.append('tags', JSON.stringify(currentTags));
                formData.append('photos', JSON.stringify(photoUrls));
                formData.append('uploaded_files', JSON.stringify(uploadedFileUrls));
                formData.append('platforms', JSON.stringify(platforms));
                formData.append('scheduled_time', scheduledTime.replace('T', ' '));
                // Save cross-promo state
                const crossPromoEnabled = document.getElementById('chk-cross-promo')?.checked || false;
                formData.append('cross_promo', crossPromoEnabled ? '1' : '0');

                const response = await fetch('api.php', { method: 'POST', body: formData });
                const data = await response.json();

                if (data.error) {
                    showNotification('Ошибка: ' + data.error, 'error');
                } else {
                    showNotification(isEditing ? 'Пост обновлён!' : 'Пост запланирован!', 'success');

                    // Clear form
                    state.selectedPhotos = [];
                    state.uploadedFiles = [];
                    updatePostingPreview();
                    updateSelectionUI();
                    syncGallerySelection();
                    if (postText) postText.value = '';
                    if (typeof tagContexts !== 'undefined') {
                        tagContexts.post = [];
                        const tagsList = document.getElementById('post-tags-list');
                        if (tagsList) tagsList.innerHTML = '';
                    }
                    clearPostDraft();

                    // Reset schedule UI and editing state
                    document.getElementById('chk-schedule').checked = false;
                    scheduledState.editingPostId = null;
                    updateScheduleUI();

                    // Refresh scheduled posts list
                    loadScheduledPosts();
                }
            } catch (error) {
                showNotification('Ошибка: ' + error.message, 'error');
            } finally {
                if (btnSendPost) {
                    btnSendPost.disabled = false;
                    btnSendPost.innerHTML = '📅 Запланировать';
                }
            }
            return;
        }

        if (btnSendPost) {
            btnSendPost.disabled = true;
            btnSendPost.textContent = 'Публикация...';
        }

        try {
            // Check if there are files still uploading
            const stillUploading = state.uploadedFiles.some(f => f.uploading);
            if (stillUploading) {
                showNotification('Дождитесь завершения загрузки файлов', 'error');
                if (btnSendPost) {
                    btnSendPost.disabled = false;
                    btnSendPost.textContent = 'Опубликовать';
                }
                return;
            }

            const photoUrls = state.selectedPhotos.map(p => p.urls.large || p.urls.original || p.urls.medium640);

            // Get uploaded files that finished uploading
            const uploadedFileUrls = state.uploadedFiles
                .filter(f => f.url && !f.uploading)
                .map(f => ({ url: f.url, type: f.type }));

            // VK has a max of 9 attachments per post. If we're posting to VK and
            // the total media exceeds that, ask the user whether to truncate.
            const totalMedia = photoUrls.length + uploadedFileUrls.length;
            if (postToVk && totalMedia > DEFAULT_MAX_PHOTOS) {
                const ok = confirm(
                    `Выбрано ${totalMedia} фото/видео, а VK принимает максимум ${DEFAULT_MAX_PHOTOS} в одной публикации.\n\n` +
                    `В VK уйдут только первые ${DEFAULT_MAX_PHOTOS}. В Telegram отправятся все.\n\nПродолжить?`
                );
                if (!ok) {
                    if (btnSendPost) {
                        btnSendPost.disabled = false;
                        btnSendPost.textContent = 'Опубликовать';
                    }
                    return;
                }
            }

            // Check cross-promo settings
            const crossPromoEnabled = document.getElementById('chk-cross-promo')?.checked;
            // Get settings from localStorage, but also check current input values as fallback
            const storedSettings = getCrossPromoSettings();
            const crossPromoSettings = {
                telegramLink: storedSettings.telegramLink || document.getElementById('cross-promo-telegram')?.value.trim() || '',
                vkLink: storedSettings.vkLink || document.getElementById('cross-promo-vk')?.value.trim() || '',
                textForTg: storedSettings.textForTg || document.getElementById('cross-promo-text-tg')?.value.trim() || 'Мой канал ВКонтакте',
                textForVk: storedSettings.textForVk || document.getElementById('cross-promo-text-vk')?.value.trim() || 'Мой канал в Telegram'
            };

            // Prepare platform-specific texts
            let textForTelegram = baseText;
            let textForVk = baseText;

            if (crossPromoEnabled) {
                console.log('Cross-promo enabled, settings:', crossPromoSettings);
                // Add VK link to Telegram post
                if (crossPromoSettings.vkLink && postToTelegram) {
                    const linkText = crossPromoSettings.textForTg || 'Мой канал ВКонтакте';
                    if (parseMode === 'HTML') {
                        textForTelegram += `\n\n<a href="${crossPromoSettings.vkLink}">${linkText}</a>`;
                    } else if (parseMode === 'Markdown') {
                        textForTelegram += `\n\n[${linkText}](${crossPromoSettings.vkLink})`;
                    } else {
                        textForTelegram += `\n\n${linkText}: ${crossPromoSettings.vkLink}`;
                    }
                }
                // Add Telegram link to VK post
                if (crossPromoSettings.telegramLink && postToVk) {
                    const linkText = crossPromoSettings.textForVk || 'Мой канал в Telegram';
                    textForVk += `\n\n${linkText}: ${crossPromoSettings.telegramLink}`;
                }
            }

            // Send to each platform with appropriate text
            const results = {};

            for (const platform of platforms) {
                // VK: hard-trim combined media to the platform limit.
                let photosForPlatform = photoUrls;
                let uploadedForPlatform = uploadedFileUrls;
                if (platform.type === 'vk') {
                    const cap = DEFAULT_MAX_PHOTOS;
                    if (photoUrls.length >= cap) {
                        photosForPlatform = photoUrls.slice(0, cap);
                        uploadedForPlatform = [];
                    } else {
                        photosForPlatform = photoUrls;
                        uploadedForPlatform = uploadedFileUrls.slice(0, cap - photoUrls.length);
                    }
                }

                const formData = new FormData();
                formData.append('action', 'multi_post');
                formData.append('platforms', JSON.stringify([platform]));
                formData.append('text', platform.type === 'telegram' ? textForTelegram : textForVk);
                formData.append('photos', JSON.stringify(photosForPlatform));
                formData.append('uploaded_files', JSON.stringify(uploadedForPlatform));
                formData.append('parse_mode', parseMode);

                const response = await fetch('api.php', { method: 'POST', body: formData });
                const data = await response.json();

                if (data.results) {
                    Object.assign(results, data.results);
                } else if (data.error) {
                    results[platform.type] = { success: false, error: data.error };
                }
            }

            // Process results
            let resultText = 'Результаты:\n';
            let hasErrors = false;

            Object.entries(results).forEach(([platform, result]) => {
                const name = platform === 'telegram' ? 'Telegram' : platform === 'vk' ? 'ВКонтакте' : platform;
                if (result.success) {
                    if (result.warning) {
                        resultText += `⚠ ${name}: Опубликовано с предупреждением\n   ${result.warning}\n`;
                    } else {
                        resultText += `✓ ${name}: Успешно\n`;
                    }
                } else {
                    resultText += `✗ ${name}: ${result.error}\n`;
                    hasErrors = true;
                }
            });

            if (postResult) {
                postResult.className = hasErrors ? 'result-message error' : 'result-message success';
                postResult.textContent = resultText;
                postResult.style.display = 'block';
            }

            if (!hasErrors) {
                state.selectedPhotos = [];
                state.uploadedFiles = []; // Clear uploaded files
                updatePostingPreview();
                updateSelectionUI();
                syncGallerySelection();
                if (postText) postText.value = '';
                // Clear tags
                if (typeof tagContexts !== 'undefined') {
                    tagContexts.post = [];
                    const tagsList = document.getElementById('post-tags-list');
                    if (tagsList) tagsList.innerHTML = '';
                }
                // Clear draft
                clearPostDraft();
                showNotification('Публикация завершена!', 'success');
            }
        } catch (error) {
            if (postResult) {
                postResult.className = 'result-message error';
                postResult.textContent = 'Ошибка: ' + error.message;
                postResult.style.display = 'block';
            }
        } finally {
            if (btnSendPost) {
                btnSendPost.disabled = false;
                btnSendPost.textContent = 'Опубликовать';
            }
        }
    });

    // ============ SETTINGS ============

    document.getElementById('btn-change-password')?.addEventListener('click', async () => {
        const currentPassword = document.getElementById('current-password')?.value;
        const newPassword = document.getElementById('new-password')?.value;
        const confirmPassword = document.getElementById('confirm-password')?.value;

        if (!currentPassword || !newPassword) {
            showNotification('Заполните все поля', 'error');
            return;
        }
        if (newPassword !== confirmPassword) {
            showNotification('Пароли не совпадают', 'error');
            return;
        }
        if (newPassword.length < 8) {
            showNotification('Минимум 8 символов', 'error');
            return;
        }

        try {
            const formData = new FormData();
            formData.append('action', 'change_password');
            formData.append('current_password', currentPassword);
            formData.append('new_password', newPassword);

            const response = await fetch('api.php', { method: 'POST', body: formData });
            const data = await response.json();

            if (data.error) {
                showNotification('Ошибка: ' + data.error, 'error');
            } else {
                showNotification('Пароль изменён', 'success');
                document.getElementById('current-password').value = '';
                document.getElementById('new-password').value = '';
                document.getElementById('confirm-password').value = '';
            }
        } catch (error) {
            showNotification('Ошибка: ' + error.message, 'error');
        }
    });

    // ============ VK TOKEN MANAGEMENT ============

    // Toggle VK token visibility
    document.getElementById('btn-toggle-vk-token')?.addEventListener('click', () => {
        const input = document.getElementById('vk-token-input');
        if (input) {
            input.type = input.type === 'password' ? 'text' : 'password';
        }
    });

    // Save VK token
    document.getElementById('btn-save-vk-token')?.addEventListener('click', async () => {
        const tokenInput = document.getElementById('vk-token-input');
        const token = tokenInput?.value.trim();
        const statusSpan = document.getElementById('vk-token-save-status');

        if (!token) {
            showNotification('Введите токен', 'error');
            return;
        }

        const btn = document.getElementById('btn-save-vk-token');
        if (btn) {
            btn.disabled = true;
            btn.textContent = 'Сохранение...';
        }

        try {
            const formData = new FormData();
            formData.append('action', 'save_vk_token');
            formData.append('token', token);

            const response = await fetch('api.php', { method: 'POST', body: formData });
            const data = await response.json();

            if (data.error) {
                showNotification('Ошибка: ' + data.error, 'error');
                if (statusSpan) {
                    statusSpan.textContent = '✗ Ошибка';
                    statusSpan.className = 'save-status error';
                }
            } else {
                let message = 'Токен сохранён';
                if (data.validation?.valid) {
                    const type = data.validation.type === 'user' ? 'пользовательский' : 'community';
                    const name = data.validation.user_name || '';
                    message += ` (${type}${name ? ': ' + name : ''})`;

                    // Update VK status display
                    const vkStatus = document.getElementById('vk-status');
                    if (vkStatus) {
                        vkStatus.textContent = `Подключён (${type})`;
                        vkStatus.className = 'status connected';
                    }
                }
                showNotification(message, 'success');
                if (statusSpan) {
                    statusSpan.textContent = '✓ Сохранено';
                    statusSpan.className = 'save-status success';
                }

                // Reload VK groups
                loadVkGroups();
            }
        } catch (error) {
            showNotification('Ошибка: ' + error.message, 'error');
            if (statusSpan) {
                statusSpan.textContent = '✗ Ошибка';
                statusSpan.className = 'save-status error';
            }
        } finally {
            if (btn) {
                btn.disabled = false;
                btn.textContent = 'Сохранить';
            }
        }
    });

    // ============ TAGS MANAGEMENT SYSTEM ============

    const TAGS_STORAGE_KEY = 'vh_tags_history';
    const MAX_TAGS_HISTORY = 100;

    // Store for current tags in each context
    const tagContexts = {
        post: [],
        converter: []
    };

    // Load tags history from localStorage
    function getTagsHistory() {
        try {
            const data = localStorage.getItem(TAGS_STORAGE_KEY);
            return data ? JSON.parse(data) : [];
        } catch (e) {
            return [];
        }
    }

    // Save tag to history
    function saveTagToHistory(tag) {
        const history = getTagsHistory();
        const existingIndex = history.findIndex(t => t.name.toLowerCase() === tag.toLowerCase());

        if (existingIndex >= 0) {
            // Increment usage count
            history[existingIndex].count++;
            history[existingIndex].lastUsed = Date.now();
        } else {
            // Add new tag
            history.push({
                name: tag,
                count: 1,
                lastUsed: Date.now()
            });
        }

        // Sort by count and limit
        history.sort((a, b) => b.count - a.count);
        if (history.length > MAX_TAGS_HISTORY) {
            history.splice(MAX_TAGS_HISTORY);
        }

        localStorage.setItem(TAGS_STORAGE_KEY, JSON.stringify(history));
    }

    // Get tag suggestions based on input
    function getTagSuggestions(input, excludeTags = []) {
        const history = getTagsHistory();
        const search = input.toLowerCase();

        return history
            .filter(t => t.name.toLowerCase().includes(search) && !excludeTags.includes(t.name))
            .slice(0, 10);
    }

    // Initialize tag input for a context
    function initTagInput(context, inputId, listId, suggestionsId) {
        const input = document.getElementById(inputId);
        const list = document.getElementById(listId);
        const suggestions = document.getElementById(suggestionsId);

        if (!input || !list) return;

        // Render current tags
        function renderTags() {
            list.innerHTML = tagContexts[context].map(tag => `
                <span class="tag-chip">
                    #${escapeHtml(tag)}
                    <button type="button" class="tag-remove" data-tag="${escapeHtml(tag)}" title="Удалить">×</button>
                </span>
            `).join('');
        }

        // Add tag
        function addTag(tag) {
            tag = tag.trim().replace(/^#/, '').replace(/[,\s]+/g, '');
            if (!tag || tagContexts[context].includes(tag)) return;

            tagContexts[context].push(tag);
            saveTagToHistory(tag);
            renderTags();
            input.value = '';
            hideSuggestions();
            // Save draft immediately when tags change in post context
            if (context === 'post') {
                savePostDraftNow();
            }
        }

        // Remove tag
        function removeTag(tag) {
            tagContexts[context] = tagContexts[context].filter(t => t !== tag);
            renderTags();
            // Save draft immediately when tags change in post context
            if (context === 'post') {
                savePostDraftNow();
            }
        }

        // Show suggestions
        function showSuggestions(query) {
            if (!suggestions) return;
            const items = getTagSuggestions(query, tagContexts[context]);

            if (items.length === 0 && query.length < 2) {
                // Show recent tags if no query
                const recent = getTagsHistory().slice(0, 8);
                if (recent.length > 0) {
                    suggestions.innerHTML = recent.map(t => `
                        <div class="tag-suggestion" data-tag="${escapeHtml(t.name)}">
                            <span>#${escapeHtml(t.name)}</span>
                            <span class="tag-count">${t.count}×</span>
                        </div>
                    `).join('');
                    suggestions.classList.add('visible');
                }
                return;
            }

            if (items.length === 0) {
                hideSuggestions();
                return;
            }

            suggestions.innerHTML = items.map(t => `
                <div class="tag-suggestion" data-tag="${escapeHtml(t.name)}">
                    <span>#${escapeHtml(t.name)}</span>
                    <span class="tag-count">${t.count}×</span>
                </div>
            `).join('');
            suggestions.classList.add('visible');
        }

        function hideSuggestions() {
            if (suggestions) suggestions.classList.remove('visible');
        }

        // Event listeners
        input.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' || e.key === ',') {
                e.preventDefault();
                addTag(input.value);
            } else if (e.key === 'Backspace' && !input.value && tagContexts[context].length > 0) {
                removeTag(tagContexts[context][tagContexts[context].length - 1]);
            } else if (e.key === 'Escape') {
                hideSuggestions();
            }
        });

        input.addEventListener('input', () => {
            showSuggestions(input.value);
        });

        input.addEventListener('focus', () => {
            showSuggestions(input.value);
        });

        input.addEventListener('blur', () => {
            setTimeout(hideSuggestions, 200);
        });

        // Click on suggestion
        if (suggestions) {
            suggestions.addEventListener('click', (e) => {
                const item = e.target.closest('.tag-suggestion');
                if (item) {
                    addTag(item.dataset.tag);
                }
            });
        }

        // Click remove button
        list.addEventListener('click', (e) => {
            if (e.target.classList.contains('tag-remove')) {
                removeTag(e.target.dataset.tag);
            }
        });

        renderTags();
    }

    // ============ TAG PRESETS MANAGEMENT (Server-side storage) ============

    let editingPresetId = null;
    let cachedPresets = []; // In-memory cache for sync operations

    // Load presets from server
    async function loadPresetsFromServer() {
        try {
            const response = await fetch('api.php?action=get_presets');
            const data = await response.json();
            if (data.success && data.presets) {
                cachedPresets = data.presets;
                return data.presets;
            }
            return [];
        } catch (e) {
            console.error('Failed to load presets:', e);
            return [];
        }
    }

    // Save presets to server
    async function savePresetsToServer(presets) {
        try {
            const response = await fetch('api.php?action=save_presets', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ presets })
            });
            const data = await response.json();
            if (data.success) {
                cachedPresets = presets;
                return true;
            }
            console.error('Failed to save presets:', data.error);
            return false;
        } catch (e) {
            console.error('Failed to save presets:', e);
            return false;
        }
    }

    // Get presets (from cache for sync operations)
    function getPresets() {
        return cachedPresets;
    }

    // Generate unique ID
    function generatePresetId() {
        const maxId = cachedPresets.reduce((max, p) => Math.max(max, p.id || 0), 0);
        return maxId + 1;
    }

    // Render presets in both containers
    function renderPresets() {
        const presets = getPresets();
        const containers = ['post-presets-list', 'converter-presets-list'];

        containers.forEach(containerId => {
            const container = document.getElementById(containerId);
            if (!container) return;

            const target = containerId.includes('converter') ? 'converter' : 'post';

            container.innerHTML = presets.map(preset => `
                <button type="button" class="tag-preset" data-preset-id="${preset.id}" data-tags="${preset.tags.join(',')}" data-target="${target}">
                    ${escapeHtml(preset.name)}
                </button>
            `).join('');

            // Add click handlers
            container.querySelectorAll('.tag-preset').forEach(btn => {
                btn.addEventListener('click', () => {
                    applyPreset(btn.dataset.presetId, btn.dataset.target);
                });
            });
        });
    }

    // Apply preset tags
    function applyPreset(presetId, target) {
        const presets = getPresets();
        const preset = presets.find(p => p.id == presetId);
        if (!preset) return;

        preset.tags.forEach(tag => {
            tag = tag.trim();
            if (tag && !tagContexts[target].includes(tag)) {
                tagContexts[target].push(tag);
                saveTagToHistory(tag);
            }
        });

        // Re-render tags
        const listId = target === 'converter' ? 'converter-tags-list' : 'post-tags-list';
        const list = document.getElementById(listId);
        if (list) {
            list.innerHTML = tagContexts[target].map(tag => `
                <span class="tag-chip">
                    #${escapeHtml(tag)}
                    <button type="button" class="tag-remove" data-tag="${escapeHtml(tag)}" title="Удалить">×</button>
                </span>
            `).join('');
        }
    }

    // Open preset modal
    function openPresetModal(mode = 'list') {
        const modal = document.getElementById('preset-modal');
        const formSection = document.getElementById('preset-form-section');
        const listSection = document.getElementById('preset-list-section');
        const titleEl = document.getElementById('preset-modal-title');

        if (mode === 'list') {
            formSection.style.display = 'none';
            listSection.style.display = 'block';
            titleEl.textContent = 'Управление пресетами';
            renderPresetManager();
        } else if (mode === 'add') {
            formSection.style.display = 'block';
            listSection.style.display = 'none';
            titleEl.textContent = 'Добавить пресет';
            document.getElementById('preset-name').value = '';
            document.getElementById('preset-tags').value = '';
            editingPresetId = null;
        } else if (mode === 'edit') {
            formSection.style.display = 'block';
            listSection.style.display = 'none';
            titleEl.textContent = 'Редактировать пресет';
        }

        modal.style.display = 'flex';
    }

    // Close preset modal
    function closePresetModal() {
        document.getElementById('preset-modal').style.display = 'none';
        editingPresetId = null;
    }

    // Render preset manager list
    function renderPresetManager() {
        const presets = getPresets();
        const container = document.getElementById('preset-manager-list');

        if (presets.length === 0) {
            container.innerHTML = '<p class="text-muted">Нет пресетов. Добавьте первый!</p>';
            return;
        }

        container.innerHTML = presets.map(preset => `
            <div class="preset-manager-item" data-preset-id="${preset.id}">
                <div class="preset-info">
                    <span class="preset-name">${escapeHtml(preset.name)}</span>
                    <span class="preset-tags-preview">${preset.tags.map(t => '#' + escapeHtml(t)).join(' ')}</span>
                </div>
                <div class="preset-actions">
                    <button type="button" class="btn-icon preset-edit" data-preset-id="${preset.id}" title="Редактировать">✏️</button>
                    <button type="button" class="btn-icon preset-delete" data-preset-id="${preset.id}" title="Удалить">🗑️</button>
                </div>
            </div>
        `).join('');

        // Add event handlers
        container.querySelectorAll('.preset-edit').forEach(btn => {
            btn.addEventListener('click', () => editPreset(btn.dataset.presetId));
        });

        container.querySelectorAll('.preset-delete').forEach(btn => {
            btn.addEventListener('click', () => deletePreset(btn.dataset.presetId));
        });
    }

    // Edit preset
    function editPreset(presetId) {
        const presets = getPresets();
        const preset = presets.find(p => p.id == presetId);
        if (!preset) return;

        document.getElementById('preset-name').value = preset.name;
        document.getElementById('preset-tags').value = preset.tags.join(', ');
        editingPresetId = preset.id;

        openPresetModal('edit');
    }

    // Delete preset
    async function deletePreset(presetId) {
        if (!confirm('Удалить этот пресет?')) return;

        let presets = getPresets();
        presets = presets.filter(p => p.id != presetId);

        if (await savePresetsToServer(presets)) {
            renderPresetManager();
            renderPresets();
            showNotification('Пресет удалён', 'success');
        } else {
            showNotification('Ошибка удаления пресета', 'error');
        }
    }

    // Save preset (add or edit)
    async function savePreset() {
        const name = document.getElementById('preset-name').value.trim();
        const tagsStr = document.getElementById('preset-tags').value.trim();

        if (!name) {
            alert('Введите название пресета');
            return;
        }

        if (!tagsStr) {
            alert('Введите хотя бы один тег');
            return;
        }

        const tags = tagsStr.split(',').map(t => t.trim()).filter(t => t);
        let presets = [...getPresets()];

        if (editingPresetId) {
            // Edit existing
            const index = presets.findIndex(p => p.id == editingPresetId);
            if (index !== -1) {
                presets[index].name = name;
                presets[index].tags = tags;
            }
        } else {
            // Add new
            presets.push({
                id: generatePresetId(),
                name: name,
                tags: tags
            });
        }

        if (await savePresetsToServer(presets)) {
            renderPresets();
            openPresetModal('list');
            showNotification('Пресет сохранён', 'success');
        } else {
            showNotification('Ошибка сохранения пресета', 'error');
        }
    }

    // Initialize presets system
    async function initTagPresets() {
        // Load presets from server
        await loadPresetsFromServer();

        // Initial render
        renderPresets();

        // Add button handlers
        document.getElementById('post-preset-add')?.addEventListener('click', () => openPresetModal('add'));
        document.getElementById('converter-preset-add')?.addEventListener('click', () => openPresetModal('add'));
        document.getElementById('post-preset-manage')?.addEventListener('click', () => openPresetModal('list'));
        document.getElementById('converter-preset-manage')?.addEventListener('click', () => openPresetModal('list'));

        // Modal handlers
        document.getElementById('preset-modal-close')?.addEventListener('click', closePresetModal);
        document.getElementById('preset-add-new')?.addEventListener('click', () => openPresetModal('add'));
        document.getElementById('preset-save')?.addEventListener('click', savePreset);
        document.getElementById('preset-cancel')?.addEventListener('click', () => openPresetModal('list'));

        // Close on overlay click
        document.getElementById('preset-modal')?.addEventListener('click', (e) => {
            if (e.target.id === 'preset-modal') closePresetModal();
        });
    }

    // Get tags as formatted string
    function getTagsString(context) {
        return tagContexts[context].map(t => '#' + t).join(' ');
    }

    // Initialize tag inputs
    initTagInput('post', 'post-tags-input', 'post-tags-list', 'post-tags-suggestions');
    initTagInput('converter', 'converter-tags-input', 'converter-tags-list', 'converter-tags-suggestions');
    initTagPresets();

    // Restore draft tags after tagContexts is initialized
    restoreDraftTags();

    // ============ TEXT FORMATTING TOOLBAR ============

    function initTextEditorToolbar() {
        document.querySelectorAll('.toolbar-btn[data-format]').forEach(btn => {
            btn.addEventListener('click', () => {
                const format = btn.dataset.format;
                const targetId = btn.dataset.target || 'post-text';
                const textarea = document.getElementById(targetId);

                if (!textarea) return;

                const start = textarea.selectionStart;
                const end = textarea.selectionEnd;
                const text = textarea.value;
                const selected = text.substring(start, end);

                let before = '', after = '';
                let newText = selected;

                // Get format mode from the post-parse-mode selector
                const parseMode = document.getElementById('post-parse-mode')?.value || 'HTML';

                if (parseMode === 'HTML') {
                    switch (format) {
                        case 'bold': before = '<b>'; after = '</b>'; break;
                        case 'italic': before = '<i>'; after = '</i>'; break;
                        case 'underline': before = '<u>'; after = '</u>'; break;
                        case 'strike': before = '<s>'; after = '</s>'; break;
                        case 'code': before = '<code>'; after = '</code>'; break;
                        case 'link':
                            const url = prompt('Введите URL:', 'https://');
                            if (url) {
                                before = '<a href="' + url + '">';
                                after = '</a>';
                                if (!selected) newText = url;
                            }
                            break;
                    }
                } else if (parseMode === 'Markdown') {
                    switch (format) {
                        case 'bold': before = '**'; after = '**'; break;
                        case 'italic': before = '_'; after = '_'; break;
                        case 'underline': before = '__'; after = '__'; break;
                        case 'strike': before = '~~'; after = '~~'; break;
                        case 'code': before = '`'; after = '`'; break;
                        case 'link':
                            const url = prompt('Введите URL:', 'https://');
                            if (url) {
                                before = '[';
                                after = '](' + url + ')';
                                if (!selected) newText = 'ссылка';
                            }
                            break;
                    }
                } else {
                    // Plain text - just insert without formatting
                    if (format === 'link') {
                        const url = prompt('Введите URL:', 'https://');
                        if (url) {
                            newText = url;
                        }
                    }
                    before = '';
                    after = '';
                }

                if (before || after || newText !== selected) {
                    const replacement = before + newText + after;
                    textarea.value = text.substring(0, start) + replacement + text.substring(end);
                    textarea.focus();
                    textarea.setSelectionRange(start + before.length, start + before.length + newText.length);
                }
            });
        });
    }

    initTextEditorToolbar();

    // ============ SCHEDULED POSTS ============

    const scheduledState = {
        uploadedFiles: [],
        editingPostId: null
    };

    // Schedule toggle handler
    const scheduleCheckbox = document.getElementById('chk-schedule');
    const scheduleOptions = document.getElementById('schedule-options');
    const btnSendPostMain = document.getElementById('btn-send-post');

    function updateScheduleUI() {
        const isScheduled = scheduleCheckbox?.checked;
        if (scheduleOptions) {
            scheduleOptions.classList.toggle('hidden', !isScheduled);
        }
        if (btnSendPostMain) {
            btnSendPostMain.innerHTML = isScheduled ? '📅 Запланировать' : '🚀 Опубликовать';
        }
    }

    scheduleCheckbox?.addEventListener('change', updateScheduleUI);

    // Schedule date/time picker
    const scheduleDate = document.getElementById('schedule-date');
    const scheduleTime = document.getElementById('schedule-time');
    const scheduledDatetime = document.getElementById('scheduled-datetime');

    // Set default to 1 hour from now
    function setDefaultScheduleTime() {
        const oneHourLater = new Date();
        oneHourLater.setHours(oneHourLater.getHours() + 1);
        oneHourLater.setMinutes(0, 0, 0); // Round to nearest hour
        setScheduleDateTime(oneHourLater);
    }

    function setScheduleDateTime(date) {
        if (scheduleDate) {
            // Use local date components to avoid timezone issues
            const year = date.getFullYear();
            const month = String(date.getMonth() + 1).padStart(2, '0');
            const day = String(date.getDate()).padStart(2, '0');
            scheduleDate.value = `${year}-${month}-${day}`;
        }
        if (scheduleTime) {
            const hours = String(date.getHours()).padStart(2, '0');
            const minutes = String(date.getMinutes()).padStart(2, '0');
            scheduleTime.value = `${hours}:${minutes}`;
        }
        syncScheduledDatetime();
    }

    function syncScheduledDatetime() {
        if (scheduleDate && scheduleTime && scheduledDatetime) {
            scheduledDatetime.value = `${scheduleDate.value}T${scheduleTime.value}`;
        }
    }

    // Sync hidden field when date/time changes
    scheduleDate?.addEventListener('change', syncScheduledDatetime);
    scheduleTime?.addEventListener('change', syncScheduledDatetime);

    // Set min date to today (local date)
    if (scheduleDate) {
        const today = new Date();
        const year = today.getFullYear();
        const month = String(today.getMonth() + 1).padStart(2, '0');
        const day = String(today.getDate()).padStart(2, '0');
        scheduleDate.min = `${year}-${month}-${day}`;
    }

    // Handle preset buttons
    document.querySelectorAll('.schedule-presets .preset-btn').forEach(btn => {
        btn.addEventListener('click', () => {
            const preset = btn.dataset.preset;
            const now = new Date();
            let targetDate = new Date();

            switch (preset) {
                case '1h':
                    targetDate.setHours(now.getHours() + 1);
                    break;
                case '3h':
                    targetDate.setHours(now.getHours() + 3);
                    break;
                case 'tomorrow-10':
                    targetDate.setDate(now.getDate() + 1);
                    targetDate.setHours(10, 0, 0, 0);
                    break;
                case 'tomorrow-18':
                    targetDate.setDate(now.getDate() + 1);
                    targetDate.setHours(18, 0, 0, 0);
                    break;
            }

            setScheduleDateTime(targetDate);

            // Highlight active preset
            document.querySelectorAll('.schedule-presets .preset-btn').forEach(b => b.classList.remove('active'));
            btn.classList.add('active');
        });
    });

    // Initialize with default time
    setDefaultScheduleTime();

    // Load scheduled and published posts on posting tab switch
    document.querySelectorAll('.nav-btn[data-tab="posting"]').forEach(btn => {
        btn.addEventListener('click', () => {
            loadScheduledPosts();
            loadPublishedPosts();
            updateScheduledCount();
        });
    });

    async function loadScheduledPosts() {
        const list = document.getElementById('scheduled-posts-list');
        if (!list) return;

        try {
            const formData = new FormData();
            formData.append('action', 'get_scheduled_posts');
            const response = await fetch('api.php', { method: 'POST', body: formData });
            const data = await response.json();

            if (data.posts && data.posts.length > 0) {
                const pendingPosts = data.posts.filter(p => p.status === 'pending');
                if (pendingPosts.length === 0) {
                    list.innerHTML = '<p class="placeholder">Нет запланированных постов</p>';
                    return;
                }

                list.innerHTML = pendingPosts.map(post => {
                    const dateStr = new Date(post.scheduled_time).toLocaleString('ru-RU', {
                        day: 'numeric',
                        month: 'short',
                        hour: '2-digit',
                        minute: '2-digit'
                    });
                    const platforms = (post.platforms || []).map(p => {
                        const type = p.type || p;
                        return type === 'telegram' ? 'TG' : type === 'vk' ? 'VK' : type;
                    }).join(' · ');

                    // Collect all photo URLs for preview
                    const allPhotos = [
                        ...(post.photos || []),
                        ...(post.uploaded_files || []).map(f => f.url || f)
                    ];
                    const photosCount = allPhotos.length;

                    // Generate photo preview HTML (show up to 4 photos)
                    let photosPreviewHtml = '';
                    if (photosCount > 0) {
                        const previewPhotos = allPhotos.slice(0, 4);
                        const moreCount = photosCount > 4 ? photosCount - 4 : 0;
                        photosPreviewHtml = `
                            <div class="scheduled-photos-preview">
                                ${previewPhotos.map(url => `<img src="${url}" alt="" class="scheduled-thumb" loading="lazy">`).join('')}
                                ${moreCount > 0 ? `<span class="more-photos">+${moreCount}</span>` : ''}
                            </div>
                        `;
                    }

                    return `
                        <div class="scheduled-post-card" data-id="${post.id}">
                            <div class="scheduled-post-header">
                                <span class="scheduled-time">📅 ${dateStr}</span>
                                <span class="scheduled-platforms">${platforms}</span>
                            </div>
                            ${photosPreviewHtml}
                            <div class="scheduled-post-content">
                                ${post.text ? `<p class="scheduled-text">${escapeHtml(post.text.substring(0, 150))}${post.text.length > 150 ? '...' : ''}</p>` : ''}
                                ${post.tags?.length ? `<span class="scheduled-tags">${post.tags.slice(0, 5).map(t => '#' + t).join(' ')}</span>` : ''}
                            </div>
                            <div class="scheduled-post-actions">
                                <button class="btn btn-small btn-secondary btn-edit-scheduled" data-id="${post.id}">✏️</button>
                                <button class="btn btn-small btn-danger btn-delete-scheduled" data-id="${post.id}">🗑️</button>
                            </div>
                        </div>
                    `;
                }).join('');

                // Attach event handlers
                list.querySelectorAll('.btn-delete-scheduled').forEach(btn => {
                    btn.addEventListener('click', () => deleteScheduledPost(btn.dataset.id));
                });
                list.querySelectorAll('.btn-edit-scheduled').forEach(btn => {
                    btn.addEventListener('click', () => editScheduledPost(btn.dataset.id, data.posts));
                });
                // Update count badge
                updateScheduledCount(pendingPosts.length);
            } else {
                list.innerHTML = '<p class="placeholder">Нет запланированных постов</p>';
                updateScheduledCount(0);
            }
        } catch (error) {
            list.innerHTML = '<p class="placeholder error">Ошибка загрузки</p>';
        }
    }

    // Update scheduled posts count badge
    async function updateScheduledCount(count = null) {
        const badge = document.getElementById('scheduled-count');
        if (!badge) return;

        if (count !== null) {
            badge.textContent = count;
            badge.style.display = count > 0 ? 'inline-block' : 'none';
            return;
        }

        try {
            const formData = new FormData();
            formData.append('action', 'get_scheduled_posts');
            const response = await fetch('api.php', { method: 'POST', body: formData });
            const data = await response.json();

            if (data.posts) {
                const pendingCount = data.posts.filter(p => p.status === 'pending').length;
                badge.textContent = pendingCount;
                badge.style.display = pendingCount > 0 ? 'inline-block' : 'none';
            }
        } catch (error) {
            console.error('Failed to update scheduled count:', error);
        }
    }

    async function deleteScheduledPost(postId) {
        if (!confirm('Удалить этот запланированный пост?')) return;

        try {
            const formData = new FormData();
            formData.append('action', 'delete_scheduled_post');
            formData.append('id', postId);

            const response = await fetch('api.php', { method: 'POST', body: formData });
            const data = await response.json();

            if (data.error) {
                showNotification('Ошибка: ' + data.error, 'error');
            } else {
                showNotification('Пост удалён', 'success');
                loadScheduledPosts();
            }
        } catch (error) {
            showNotification('Ошибка: ' + error.message, 'error');
        }
    }

    function editScheduledPost(postId, posts) {
        const post = posts.find(p => p.id === postId);
        if (!post) return;

        const card = document.querySelector(`.scheduled-post-card[data-id="${postId}"]`);
        if (!card) return;

        // Collect all photo URLs
        const allPhotos = [
            ...(post.photos || []),
            ...(post.uploaded_files || []).map(f => f.url || f)
        ];

        const hasTg = (post.platforms || []).some(p => (p.type || p) === 'telegram');
        const hasVk = (post.platforms || []).some(p => (p.type || p) === 'vk');
        const tags = post.tags || [];

        // Parse scheduled_time into date and time inputs
        let dateVal = '', timeVal = '';
        if (post.scheduled_time) {
            const dt = new Date(post.scheduled_time.replace(' ', 'T'));
            dateVal = `${dt.getFullYear()}-${String(dt.getMonth()+1).padStart(2,'0')}-${String(dt.getDate()).padStart(2,'0')}`;
            timeVal = `${String(dt.getHours()).padStart(2,'0')}:${String(dt.getMinutes()).padStart(2,'0')}`;
        }

        // Save original HTML for cancel
        const originalHtml = card.innerHTML;

        // Render inline editor
        card.classList.add('editing');
        card.innerHTML = `
            <div class="inline-editor">
                <div class="inline-editor-photos">
                    ${allPhotos.length > 0 ? allPhotos.map((url, i) => `
                        <div class="preview-thumb">
                            <img src="${url}" alt="">
                            <button class="remove-btn" data-index="${i}">×</button>
                        </div>
                    `).join('') : '<p class="placeholder">Нет фото</p>'}
                </div>
                <textarea class="inline-editor-text" rows="4" placeholder="Текст публикации...">${escapeHtml(post.text || '')}</textarea>
                <div class="inline-editor-tags">
                    <div class="tags-list inline-tags-list">
                        ${tags.map(t => `<span class="tag-chip">#${escapeHtml(t)} <button type="button" class="tag-remove" data-tag="${escapeHtml(t)}">×</button></span>`).join('')}
                    </div>
                    <input type="text" class="tags-input inline-tags-input" placeholder="Добавить тег...">
                </div>
                <div class="inline-editor-row">
                    <div class="inline-editor-field">
                        <label>Дата:</label>
                        <input type="date" class="schedule-input inline-date" value="${dateVal}">
                    </div>
                    <div class="inline-editor-field">
                        <label>Время:</label>
                        <input type="time" class="schedule-input inline-time" value="${timeVal}">
                    </div>
                </div>
                <div class="inline-editor-row">
                    <label class="checkbox-label compact"><input type="checkbox" class="inline-chk-tg" ${hasTg ? 'checked' : ''}> TG</label>
                    <label class="checkbox-label compact"><input type="checkbox" class="inline-chk-vk" ${hasVk ? 'checked' : ''}> VK</label>
                </div>
                <div class="inline-editor-actions">
                    <button class="btn btn-primary btn-small inline-save">💾 Сохранить</button>
                    <button class="btn btn-accent btn-small inline-publish-now">🚀 Опубликовать сейчас</button>
                    <button class="btn btn-secondary btn-small inline-cancel">Отмена</button>
                </div>
            </div>
        `;

        // Track editable photos and tags
        let editPhotos = [...allPhotos];
        let editTags = [...tags];

        // Photo remove handlers
        card.querySelectorAll('.inline-editor-photos .remove-btn').forEach(btn => {
            btn.addEventListener('click', () => {
                const idx = parseInt(btn.dataset.index);
                editPhotos.splice(idx, 1);
                // Re-render photos
                const photosContainer = card.querySelector('.inline-editor-photos');
                if (editPhotos.length > 0) {
                    photosContainer.innerHTML = editPhotos.map((url, i) => `
                        <div class="preview-thumb">
                            <img src="${url}" alt="">
                            <button class="remove-btn" data-index="${i}">×</button>
                        </div>
                    `).join('');
                    // Re-attach handlers
                    photosContainer.querySelectorAll('.remove-btn').forEach(b => {
                        b.addEventListener('click', () => {
                            editPhotos.splice(parseInt(b.dataset.index), 1);
                            b.parentElement.remove();
                            if (editPhotos.length === 0) {
                                photosContainer.innerHTML = '<p class="placeholder">Нет фото</p>';
                            }
                        });
                    });
                } else {
                    photosContainer.innerHTML = '<p class="placeholder">Нет фото</p>';
                }
            });
        });

        // Tag remove handlers
        card.querySelectorAll('.tag-remove').forEach(btn => {
            btn.addEventListener('click', () => {
                editTags = editTags.filter(t => t !== btn.dataset.tag);
                btn.parentElement.remove();
            });
        });

        // Tag add on Enter
        const tagInput = card.querySelector('.inline-tags-input');
        tagInput?.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' || e.key === ',') {
                e.preventDefault();
                const tag = tagInput.value.trim().replace(/^#/, '').replace(/[,\s]+/g, '');
                if (tag && !editTags.includes(tag)) {
                    editTags.push(tag);
                    const chip = document.createElement('span');
                    chip.className = 'tag-chip';
                    chip.innerHTML = `#${escapeHtml(tag)} <button type="button" class="tag-remove" data-tag="${escapeHtml(tag)}">×</button>`;
                    chip.querySelector('.tag-remove').addEventListener('click', () => {
                        editTags = editTags.filter(t => t !== tag);
                        chip.remove();
                    });
                    card.querySelector('.inline-tags-list').appendChild(chip);
                }
                tagInput.value = '';
            }
        });

        // Cancel
        card.querySelector('.inline-cancel').addEventListener('click', () => {
            card.classList.remove('editing');
            card.innerHTML = originalHtml;
            // Re-attach original handlers
            card.querySelector('.btn-edit-scheduled')?.addEventListener('click', () => editScheduledPost(postId, posts));
            card.querySelector('.btn-delete-scheduled')?.addEventListener('click', () => deleteScheduledPost(postId));
        });

        // Save
        card.querySelector('.inline-save').addEventListener('click', async () => {
            const text = card.querySelector('.inline-editor-text').value;
            const dateInput = card.querySelector('.inline-date').value;
            const timeInput = card.querySelector('.inline-time').value;
            const tgChecked = card.querySelector('.inline-chk-tg').checked;
            const vkChecked = card.querySelector('.inline-chk-vk').checked;

            if (!dateInput || !timeInput) {
                showNotification('Укажите дату и время', 'error');
                return;
            }

            const scheduledTime = `${dateInput} ${timeInput}:00`;

            const platforms = [];
            if (tgChecked) platforms.push({ type: 'telegram', target: document.getElementById('tg-channel')?.value || '' });
            if (vkChecked) platforms.push({ type: 'vk', target: document.getElementById('vk-group')?.value || '' });

            if (platforms.length === 0) {
                showNotification('Выберите платформу', 'error');
                return;
            }

            // Separate photos and uploaded files based on original data
            const origPhotos = post.photos || [];
            const origUploaded = (post.uploaded_files || []).map(f => f.url || f);
            const newPhotos = editPhotos.filter(url => origPhotos.includes(url));
            const newUploaded = editPhotos.filter(url => origUploaded.includes(url)).map(url => {
                const orig = (post.uploaded_files || []).find(f => (f.url || f) === url);
                return orig && typeof orig === 'object' ? orig : { url };
            });
            // Photos not in either original list go to photos
            editPhotos.forEach(url => {
                if (!newPhotos.includes(url) && !newUploaded.some(f => (f.url || f) === url)) {
                    newPhotos.push(url);
                }
            });

            const saveBtn = card.querySelector('.inline-save');
            saveBtn.disabled = true;
            saveBtn.textContent = 'Сохранение...';

            try {
                const formData = new FormData();
                formData.append('action', 'update_scheduled_post');
                formData.append('id', postId);
                formData.append('text', text);
                formData.append('tags', JSON.stringify(editTags));
                formData.append('photos', JSON.stringify(newPhotos));
                formData.append('uploaded_files', JSON.stringify(newUploaded));
                formData.append('platforms', JSON.stringify(platforms));
                formData.append('scheduled_time', scheduledTime);

                const response = await fetch('api.php', { method: 'POST', body: formData });
                const data = await response.json();

                if (data.error) {
                    showNotification('Ошибка: ' + data.error, 'error');
                    saveBtn.disabled = false;
                    saveBtn.textContent = '💾 Сохранить';
                } else {
                    showNotification('Пост обновлён!', 'success');
                    loadScheduledPosts();
                }
            } catch (error) {
                showNotification('Ошибка: ' + error.message, 'error');
                saveBtn.disabled = false;
                saveBtn.textContent = '💾 Сохранить';
            }
        });

        // Publish now
        card.querySelector('.inline-publish-now').addEventListener('click', async () => {
            const text = card.querySelector('.inline-editor-text').value;
            const tgChecked = card.querySelector('.inline-chk-tg').checked;
            const vkChecked = card.querySelector('.inline-chk-vk').checked;

            const platforms = [];
            const tgChannel = document.getElementById('tg-channel')?.value;
            const vkGroup = document.getElementById('vk-group')?.value;
            if (tgChecked && tgChannel) platforms.push({ type: 'telegram', target: tgChannel });
            if (vkChecked && vkGroup) platforms.push({ type: 'vk', target: vkGroup });

            if (platforms.length === 0) {
                showNotification('Выберите платформу и канал/группу', 'error');
                return;
            }

            if (editPhotos.length === 0 && !text.trim()) {
                showNotification('Добавьте фото или текст', 'error');
                return;
            }

            if (!confirm('Опубликовать этот пост сейчас?')) return;

            const pubBtn = card.querySelector('.inline-publish-now');
            pubBtn.disabled = true;
            pubBtn.textContent = 'Публикация...';

            try {
                // Add tags to text
                const tagsStr = editTags.map(t => '#' + t).join(' ');
                let fullText = text.trim();
                if (tagsStr) {
                    fullText = fullText ? fullText + '\n\n' + tagsStr : tagsStr;
                }

                // Add cross-promo
                const crossPromoEnabled = post.cross_promo;
                if (crossPromoEnabled) {
                    const storedSettings = getCrossPromoSettings();
                    if (tgChecked && storedSettings.vkLink) {
                        // Will be handled per-platform below
                    }
                }

                // Publish to each platform
                const results = {};
                for (const platform of platforms) {
                    let platformText = fullText;

                    // Cross-promo
                    if (crossPromoEnabled) {
                        const s = getCrossPromoSettings();
                        if (platform.type === 'telegram' && s.vkLink) {
                            const parseMode = 'HTML';
                            platformText += `\n\n<a href="${s.vkLink}">${s.textForTg || 'Мой канал ВКонтакте'}</a>`;
                        }
                        if (platform.type === 'vk' && s.telegramLink) {
                            platformText += `\n\n${s.textForVk || 'Мой канал в Telegram'}: ${s.telegramLink}`;
                        }
                    }

                    const formData = new FormData();
                    formData.append('action', 'multi_post');
                    formData.append('platforms', JSON.stringify([platform]));
                    formData.append('text', platformText);
                    formData.append('photos', JSON.stringify(editPhotos));
                    formData.append('uploaded_files', JSON.stringify([]));
                    formData.append('parse_mode', 'HTML');

                    const response = await fetch('api.php', { method: 'POST', body: formData });
                    const data = await response.json();
                    if (data.results) Object.assign(results, data.results);
                }

                // Check results
                const allSuccess = Object.values(results).every(r => r.success);
                const anySuccess = Object.values(results).some(r => r.success);

                if (anySuccess) {
                    // Mark scheduled post as published so it appears in archive
                    const markForm = new FormData();
                    markForm.append('action', 'mark_post_published');
                    markForm.append('id', postId);
                    markForm.append('results', JSON.stringify(results));
                    await fetch('api.php', { method: 'POST', body: markForm });

                    // Surface VK warnings (e.g. photos posted as links instead of attachments)
                    const warnings = Object.entries(results)
                        .filter(([, r]) => r.success && r.warning)
                        .map(([p, r]) => `${p.toUpperCase()}: ${r.warning}`);
                    if (warnings.length > 0) {
                        showNotification('Опубликовано с предупреждением — ' + warnings.join(' | '), 'warning');
                    } else {
                        showNotification(allSuccess ? 'Опубликовано!' : 'Частично опубликовано', allSuccess ? 'success' : 'warning');
                    }
                    loadScheduledPosts();
                    loadPublishedPosts();
                } else {
                    const errors = Object.entries(results).map(([p, r]) => `${p}: ${r.error}`).join(', ');
                    showNotification('Ошибка: ' + errors, 'error');
                    pubBtn.disabled = false;
                    pubBtn.textContent = '🚀 Опубликовать сейчас';
                }
            } catch (error) {
                showNotification('Ошибка: ' + error.message, 'error');
                pubBtn.disabled = false;
                pubBtn.textContent = '🚀 Опубликовать сейчас';
            }
        });

        // Scroll to the card
        card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }

    // ============ PUBLISHED POSTS ARCHIVE ============

    async function loadPublishedPosts() {
        const list = document.getElementById('published-posts-list');
        if (!list) return;

        try {
            const formData = new FormData();
            formData.append('action', 'get_published_posts');
            const response = await fetch('api.php', { method: 'POST', body: formData });
            const data = await response.json();

            if (data.posts && data.posts.length > 0) {
                list.innerHTML = data.posts.map(post => {
                    const publishedDate = post.published_at || post.scheduled_time;
                    const dateStr = new Date(publishedDate).toLocaleString('ru-RU', {
                        day: 'numeric',
                        month: 'short',
                        hour: '2-digit',
                        minute: '2-digit'
                    });

                    const platforms = (post.platforms || []).map(p => {
                        const type = p.type || p;
                        return type === 'telegram' ? 'TG' : type === 'vk' ? 'VK' : type;
                    }).join(' · ');

                    // Check results for success/error
                    const results = post.results || {};
                    let statusIcons = '';
                    let warningText = '';
                    Object.entries(results).forEach(([platform, result]) => {
                        const icon = result.success ? (result.warning ? '⚠' : '✓') : '✗';
                        const platformName = platform === 'telegram' ? 'TG' : platform === 'vk' ? 'VK' : platform;
                        const tip = result.error || result.warning || 'OK';
                        const cls = result.success ? (result.warning ? 'result-warning' : 'result-success') : 'result-error';
                        statusIcons += `<span class="${cls}" title="${escapeHtml(tip)}">${platformName} ${icon}</span> `;
                        if (result.warning) {
                            warningText = `${platformName}: ${result.warning}`;
                        }
                    });

                    // Collect all photo URLs for preview
                    const allPhotos = [
                        ...(post.photos || []),
                        ...(post.uploaded_files || []).map(f => f.url || f)
                    ];

                    // Generate photo preview HTML (show up to 3 photos)
                    let photosPreviewHtml = '';
                    if (allPhotos.length > 0) {
                        const previewPhotos = allPhotos.slice(0, 3);
                        photosPreviewHtml = `
                            <div class="archive-photos-preview">
                                ${previewPhotos.map(url => `<img src="${url}" alt="" class="archive-thumb" loading="lazy">`).join('')}
                                ${allPhotos.length > 3 ? `<span class="more-photos">+${allPhotos.length - 3}</span>` : ''}
                            </div>
                        `;
                    }

                    return `
                        <div class="archive-post-card">
                            <div class="archive-post-header">
                                <span class="archive-time">${dateStr}</span>
                                <span class="archive-results">${statusIcons}</span>
                            </div>
                            ${photosPreviewHtml}
                            ${post.text ? `<p class="archive-text">${escapeHtml(post.text.substring(0, 100))}${post.text.length > 100 ? '...' : ''}</p>` : ''}
                            ${warningText ? `<p class="archive-warning">⚠ ${escapeHtml(warningText)}</p>` : ''}
                        </div>
                    `;
                }).join('');
            } else {
                list.innerHTML = '<p class="placeholder">Нет опубликованных постов</p>';
            }
        } catch (error) {
            list.innerHTML = '<p class="placeholder error">Ошибка загрузки</p>';
        }
    }

    // Refresh archive button
    document.getElementById('btn-refresh-archive')?.addEventListener('click', loadPublishedPosts);

    // Load scheduled and published posts on initial page load
    loadScheduledPosts();
    loadPublishedPosts();

    // Create/Update scheduled post
    document.getElementById('btn-create-scheduled')?.addEventListener('click', async () => {
        const text = document.getElementById('scheduled-text')?.value || '';
        const datetime = document.getElementById('scheduled-datetime')?.value || '';
        const tgChecked = document.getElementById('scheduled-chk-telegram')?.checked;
        const vkChecked = document.getElementById('scheduled-chk-vk')?.checked;

        if (!datetime) {
            showNotification('Укажите дату и время публикации', 'error');
            return;
        }

        const platforms = [];
        if (tgChecked) platforms.push({ type: 'telegram' });
        if (vkChecked) platforms.push({ type: 'vk' });

        if (platforms.length === 0) {
            showNotification('Выберите хотя бы одну платформу', 'error');
            return;
        }

        // Get photos from gallery selection
        const photoUrls = state.selectedPhotos.map(p => p.urls.large || p.urls.original || p.urls.medium640);

        const formData = new FormData();
        formData.append('action', scheduledState.editingPostId ? 'update_scheduled_post' : 'create_scheduled_post');
        if (scheduledState.editingPostId) {
            formData.append('id', scheduledState.editingPostId);
        }
        formData.append('text', text);
        formData.append('tags', JSON.stringify([])); // TODO: add tags support
        formData.append('photos', JSON.stringify(photoUrls));
        formData.append('uploaded_files', JSON.stringify(scheduledState.uploadedFiles.filter(f => f.url)));
        formData.append('platforms', JSON.stringify(platforms));
        formData.append('scheduled_time', datetime.replace('T', ' '));

        try {
            const response = await fetch('api.php', { method: 'POST', body: formData });
            const data = await response.json();

            if (data.error) {
                showNotification('Ошибка: ' + data.error, 'error');
            } else {
                showNotification(scheduledState.editingPostId ? 'Пост обновлён' : 'Пост запланирован!', 'success');

                // Reset form
                document.getElementById('scheduled-text').value = '';
                document.getElementById('scheduled-datetime').value = '';
                scheduledState.editingPostId = null;
                scheduledState.uploadedFiles = [];
                document.getElementById('scheduled-uploaded-preview').innerHTML = '';

                const btn = document.getElementById('btn-create-scheduled');
                if (btn) btn.textContent = '📅 Запланировать публикацию';

                loadScheduledPosts();
            }
        } catch (error) {
            showNotification('Ошибка: ' + error.message, 'error');
        }
    });

    // File upload for scheduled posts
    document.getElementById('btn-scheduled-upload')?.addEventListener('click', () => {
        document.getElementById('scheduled-file-upload')?.click();
    });

    document.getElementById('scheduled-file-upload')?.addEventListener('change', async (e) => {
        const files = Array.from(e.target.files);
        if (!files.length) return;

        for (const file of files) {
            if (file.size > 50 * 1024 * 1024) {
                showNotification(`Файл ${file.name} слишком большой`, 'error');
                continue;
            }

            const formData = new FormData();
            formData.append('action', 'upload_file');
            formData.append('file', file);

            try {
                const response = await fetch('api.php', { method: 'POST', body: formData });
                const data = await response.json();

                if (data.error) {
                    showNotification(`Ошибка: ${data.error}`, 'error');
                } else {
                    scheduledState.uploadedFiles.push({
                        url: data.url,
                        type: data.type,
                        name: file.name
                    });
                    renderScheduledUploads();
                }
            } catch (error) {
                showNotification('Ошибка загрузки', 'error');
            }
        }
        e.target.value = '';
    });

    function renderScheduledUploads() {
        const preview = document.getElementById('scheduled-uploaded-preview');
        if (!preview) return;

        preview.innerHTML = scheduledState.uploadedFiles.map((file, idx) => `
            <div class="preview-item uploaded-item">
                <span class="file-name">${escapeHtml(file.name?.substring(0, 15) || 'file')}</span>
                <button type="button" class="remove-uploaded" data-idx="${idx}">&times;</button>
            </div>
        `).join('');

        preview.querySelectorAll('.remove-uploaded').forEach(btn => {
            btn.addEventListener('click', () => {
                scheduledState.uploadedFiles.splice(parseInt(btn.dataset.idx), 1);
                renderScheduledUploads();
            });
        });
    }

    // Update scheduled photos preview when switching to tab
    function updateScheduledPhotosPreview() {
        const preview = document.getElementById('scheduled-photos-preview');
        if (!preview) return;

        if (state.selectedPhotos.length === 0) {
            preview.innerHTML = '<p class="placeholder">Сначала выберите фото в Галерее, затем вернитесь сюда</p>';
        } else {
            preview.innerHTML = state.selectedPhotos.map(photo => `
                <div class="preview-item">
                    <img src="${photo.urls.small || photo.urls.thumbnail}" class="preview-thumb" alt="">
                </div>
            `).join('');
        }
    }

    // Update preview when tab is switched
    document.querySelectorAll('.nav-btn[data-tab="scheduled"]').forEach(btn => {
        btn.addEventListener('click', updateScheduledPhotosPreview);
    });

    // ============ COPY WITH TAGS ============

    document.getElementById('btn-copy-with-tags')?.addEventListener('click', () => {
        const output = document.getElementById('output-result');
        const title = document.getElementById('converter-title')?.value || '';
        const text = document.getElementById('converter-text')?.value || '';
        const tags = getTagsString('converter');

        let fullText = '';
        if (title) fullText += title + '\n\n';
        if (text) fullText += text + '\n\n';
        fullText += output.value;
        if (tags) fullText += '\n\n' + tags;

        navigator.clipboard.writeText(fullText).then(() => {
            const status = document.getElementById('copy-status');
            if (status) {
                status.textContent = '✓ Скопировано с тегами!';
                status.classList.add('visible');
                setTimeout(() => status.classList.remove('visible'), 2000);
            }
        });
    });

    // Update copy button to show status
    document.getElementById('btn-copy')?.addEventListener('click', () => {
        const output = document.getElementById('output-result');
        navigator.clipboard.writeText(output.value).then(() => {
            const status = document.getElementById('copy-status');
            if (status) {
                status.textContent = '✓ Скопировано!';
                status.classList.add('visible');
                setTimeout(() => status.classList.remove('visible'), 2000);
            }
        });
    });

    // Check Flickr OAuth status
    async function loadFlickrOAuthStatus() {
        const statusEl = document.getElementById('flickr-oauth-status');
        const btnEl = document.getElementById('flickr-oauth-btn');
        const bannerEl = document.getElementById('oauth-banner');

        try {
            const response = await fetch('api.php?action=flickr_oauth_status');
            const data = await response.json();

            if (data.authorized) {
                // Settings page elements
                if (statusEl) {
                    statusEl.className = 'status connected';
                    statusEl.textContent = 'Авторизован (оригиналы доступны)';
                }
                if (btnEl) {
                    btnEl.textContent = 'Переавторизовать';
                    btnEl.className = 'btn btn-small btn-secondary';
                }
                // Hide gallery banner when authorized
                if (bannerEl) {
                    bannerEl.classList.add('hidden');
                }
            } else {
                // Settings page elements
                if (statusEl) {
                    statusEl.className = 'status disconnected';
                    statusEl.textContent = 'Не авторизован';
                }
                if (btnEl) {
                    btnEl.textContent = 'Авторизовать';
                    btnEl.className = 'btn btn-small btn-primary';
                }
                // Show gallery banner when not authorized
                if (bannerEl) {
                    bannerEl.classList.remove('hidden');
                }
            }
        } catch (error) {
            if (statusEl) {
                statusEl.className = 'status disconnected';
                statusEl.textContent = 'Ошибка проверки';
            }
        }
    }

    // ============ WIDGET SETTINGS ============

    const widgetApiUrlInput = document.getElementById('widget-api-url');
    const widgetApiUrlCode = document.getElementById('widget-api-url-code');
    const widgetEnabled = document.getElementById('widget-enabled');
    const widgetMaxPhotos = document.getElementById('widget-max-photos');
    const widgetCacheTime = document.getElementById('widget-cache-time');
    const widgetAlbumsList = document.getElementById('widget-albums-list');
    const btnLoadWidgetAlbums = document.getElementById('btn-load-widget-albums');
    const btnSaveWidgetSettings = document.getElementById('btn-save-widget-settings');
    const widgetSaveStatus = document.getElementById('widget-save-status');

    let widgetSelectedAlbums = [];

    // Set API URL
    if (widgetApiUrlInput) {
        const apiUrl = window.location.origin + window.location.pathname.replace('index.php', '') + 'widget_api.php?action=get_photos';
        widgetApiUrlInput.value = apiUrl;
        if (widgetApiUrlCode) {
            widgetApiUrlCode.textContent = apiUrl;
        }
    }

    // Copy widget URL
    window.copyWidgetUrl = function() {
        if (widgetApiUrlInput) {
            widgetApiUrlInput.select();
            document.execCommand('copy');
            showNotification('URL скопирован', 'success');
        }
    };

    // Load widget settings
    async function loadWidgetSettings() {
        try {
            const response = await fetch('widget_api.php?action=get_settings');
            const data = await response.json();
            if (data.success && data.settings) {
                const settings = data.settings;
                if (widgetEnabled) widgetEnabled.checked = settings.enabled !== false;
                if (widgetMaxPhotos) widgetMaxPhotos.value = settings.max_photos || 30;
                if (widgetCacheTime) widgetCacheTime.value = settings.cache_time || 3600;
                widgetSelectedAlbums = settings.albums || [];
            }
        } catch (error) {
            console.error('Failed to load widget settings:', error);
        }
    }

    // Load albums for widget selection
    async function loadWidgetAlbums() {
        if (!widgetAlbumsList) return;

        widgetAlbumsList.innerHTML = '<p class="loading">Загрузка альбомов...</p>';

        try {
            const response = await fetch('widget_api.php?action=get_albums');
            const data = await response.json();

            if (data.success && data.albums) {
                renderWidgetAlbums(data.albums);
            } else {
                widgetAlbumsList.innerHTML = '<p class="error">Ошибка загрузки альбомов</p>';
            }
        } catch (error) {
            widgetAlbumsList.innerHTML = '<p class="error">Ошибка: ' + error.message + '</p>';
        }
    }

    // Render widget albums selection
    function renderWidgetAlbums(albums) {
        if (!widgetAlbumsList) return;

        if (albums.length === 0) {
            widgetAlbumsList.innerHTML = '<p class="placeholder">Нет доступных альбомов</p>';
            return;
        }

        widgetAlbumsList.innerHTML = albums.map(album => {
            const isSelected = widgetSelectedAlbums.includes(album.id);
            const title = album.title?._content || album.title || 'Без названия';
            const count = album.photos || 0;
            const thumb = album.primary_photo_extras?.url_m ||
                          album.primary_photo_extras?.url_s ||
                          album.primary_photo_extras?.url_sq || '';
            return `
                <label class="widget-album-item ${isSelected ? 'selected' : ''}" data-album-id="${album.id}">
                    <input type="checkbox" ${isSelected ? 'checked' : ''} value="${album.id}">
                    ${thumb ? `<img src="${thumb}" alt="" class="widget-album-thumb">` : '<div class="widget-album-thumb" style="background:#eee;display:flex;align-items:center;justify-content:center;">📁</div>'}
                    <span class="widget-album-title">${escapeHtml(title)}</span>
                    <span class="widget-album-count">${count} фото</span>
                </label>
            `;
        }).join('');

        // Add event listeners
        widgetAlbumsList.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
            checkbox.addEventListener('change', function() {
                const albumId = this.value;
                const label = this.closest('.widget-album-item');
                if (this.checked) {
                    if (!widgetSelectedAlbums.includes(albumId)) {
                        widgetSelectedAlbums.push(albumId);
                    }
                    label.classList.add('selected');
                } else {
                    widgetSelectedAlbums = widgetSelectedAlbums.filter(id => id !== albumId);
                    label.classList.remove('selected');
                }
            });
        });
    }

    // Save widget settings
    async function saveWidgetSettings() {
        const settings = {
            enabled: widgetEnabled ? widgetEnabled.checked : true,
            albums: widgetSelectedAlbums,
            max_photos: widgetMaxPhotos ? parseInt(widgetMaxPhotos.value) : 30,
            cache_time: widgetCacheTime ? parseInt(widgetCacheTime.value) : 3600
        };

        try {
            const response = await fetch('widget_api.php?action=save_settings', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(settings)
            });

            const data = await response.json();
            if (data.success) {
                if (widgetSaveStatus) {
                    widgetSaveStatus.textContent = '✓ Сохранено';
                    widgetSaveStatus.className = 'save-status success';
                    setTimeout(() => {
                        widgetSaveStatus.textContent = '';
                    }, 3000);
                }
                showNotification('Настройки виджета сохранены', 'success');
            } else {
                showNotification(data.error || 'Ошибка сохранения', 'error');
            }
        } catch (error) {
            showNotification('Ошибка: ' + error.message, 'error');
        }
    }

    // Widget event listeners
    if (btnLoadWidgetAlbums) {
        btnLoadWidgetAlbums.addEventListener('click', loadWidgetAlbums);
    }

    if (btnSaveWidgetSettings) {
        btnSaveWidgetSettings.addEventListener('click', saveWidgetSettings);
    }

    // Load widget settings on tab switch
    document.querySelectorAll('.nav-btn[data-tab="widget"]').forEach(btn => {
        btn.addEventListener('click', () => {
            loadWidgetSettings();
        });
    });

    // Initial load
    loadTelegramStatus();
    loadVKStatus();
    loadFlickrOAuthStatus();
    updateScheduledCount(); // Load scheduled posts count badge

    // Restore selected photos and uploaded files UI if any were saved
    if (state.selectedPhotos.length > 0 || state.uploadedFiles.length > 0) {
        updateSelectionUI();
        updatePostingPreview();
        if (state.selectedPhotos.length > 0) {
            console.log('Restored ' + state.selectedPhotos.length + ' Flickr photos from previous session');
        }
        if (state.uploadedFiles.length > 0) {
            console.log('Restored ' + state.uploadedFiles.length + ' uploaded files from previous session');
        }
    }

    // Auto-load albums on page init
    if (albumsGrid) {
        const cachedAlbums = getAlbumCache();
        if (cachedAlbums && cachedAlbums.length > 0) {
            // Use cache first for instant display
            console.log('Loading cached albums:', cachedAlbums.length);
            window._cachedAlbums = cachedAlbums;
            renderAlbumsGrid(cachedAlbums);
            // Silently refresh in background
            setTimeout(() => refreshAlbumsSilently(), 2000);
        } else {
            // No cache or empty - load from API
            console.log('No cache, loading albums from API...');
            loadAlbums(false);
        }
    }

    // ============ DIGITAL BADGE (round display) ============
    {
        const BADGE_SIZE = 240;             // Final output (round display)
        const CANVAS_DISPLAY_SIZE = 480;    // Editor internal resolution
        const FONT_STACK = '-apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';

        const badgeState = {
            items: [],
            activeId: null,
            defaultNickname: '',
        };

        const el = {
            fileInput:        document.getElementById('badge-file-upload'),
            btnUpload:        document.getElementById('btn-badge-upload'),
            btnFromFlickr:    document.getElementById('btn-badge-from-flickr'),
            itemsGrid:        document.getElementById('badge-items-grid'),
            itemsCount:       document.getElementById('badge-items-count'),
            editor:           document.getElementById('badge-editor'),
            canvas:           document.getElementById('badge-canvas'),
            zoom:             document.getElementById('badge-zoom'),
            zoomValue:        document.getElementById('badge-zoom-value'),
            btnReset:         document.getElementById('btn-badge-reset'),
            priceEnabled:     document.getElementById('badge-price-enabled'),
            priceOptions:     document.getElementById('badge-price-options'),
            priceValue:       document.getElementById('badge-price-value'),
            priceY:           document.getElementById('badge-price-y'),
            priceYValue:      document.getElementById('badge-price-y-value'),
            priceSize:        document.getElementById('badge-price-size'),
            priceSizeValue:   document.getElementById('badge-price-size-value'),
            priceBg:          document.getElementById('badge-price-bg'),
            priceFg:          document.getElementById('badge-price-fg'),
            nickEnabled:      document.getElementById('badge-nickname-enabled'),
            nickOptions:      document.getElementById('badge-nickname-options'),
            nickValue:        document.getElementById('badge-nickname-value'),
            nickColor:        document.getElementById('badge-nick-color'),
            nickSize:         document.getElementById('badge-nick-size'),
            nickSizeValue:    document.getElementById('badge-nick-size-value'),
            nickEdge:         document.getElementById('badge-nick-edge'),
            nickEdgeValue:    document.getElementById('badge-nick-edge-value'),
            nickAsPrice:      document.getElementById('badge-nick-as-price'),
            btnDownload:      document.getElementById('btn-badge-download'),
            btnRemove:        document.getElementById('btn-badge-remove'),
            batchActions:     document.getElementById('badge-batch-actions'),
            btnDownloadAll:   document.getElementById('btn-badge-download-all'),
            historyGrid:      document.getElementById('badge-history-grid'),
            // Settings tab
            nickDefault:      document.getElementById('badge-nickname-default'),
            btnSaveNick:      document.getElementById('btn-save-badge-nickname'),
            nickSaveStatus:   document.getElementById('badge-nickname-save-status'),
        };

        if (!el.canvas) return; // Defensive: tab not present.

        const ctx = el.canvas.getContext('2d');

        // Detect Web Share with file support to label the button appropriately on phones.
        const canShareFiles = (() => {
            try {
                if (typeof File === 'undefined' || !navigator.canShare) return false;
                const probe = new File([''], 'p.png', { type: 'image/png' });
                return navigator.canShare({ files: [probe] });
            } catch (e) {
                return false;
            }
        })();

        const labelEl = document.getElementById('badge-download-label');
        if (labelEl && canShareFiles) {
            labelEl.textContent = 'Сохранить в Фото';
        }

        // ---------- Drawing helpers ----------

        function formatPrice(v) {
            if (v === '' || v == null) return '';
            const n = Number(v);
            if (!isFinite(n)) return String(v);
            return Math.round(n).toLocaleString('ru-RU') + ' ₽';
        }

        function roundRect(c, x, y, w, h, r) {
            c.beginPath();
            c.moveTo(x + r, y);
            c.lineTo(x + w - r, y);
            c.quadraticCurveTo(x + w, y, x + w, y + r);
            c.lineTo(x + w, y + h - r);
            c.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
            c.lineTo(x + r, y + h);
            c.quadraticCurveTo(x, y + h, x, y + h - r);
            c.lineTo(x, y + r);
            c.quadraticCurveTo(x, y, x + r, y);
            c.closePath();
        }

        function drawPriceArc(c, size, item, text) {
            const scale = (item.priceSize || 100) / 100;
            const offsetY = ((item.priceY || 0) / 100) * size;
            const baseTopFrac = 0.74;
            const baseBottomFrac = 0.965;
            const baseHeight = (baseBottomFrac - baseTopFrac) * size;
            const height = baseHeight * scale;
            const centerY = ((baseTopFrac + baseBottomFrac) / 2) * size + offsetY;
            const yTop = centerY - height / 2;
            const yBottom = centerY + height / 2;
            const bg = item.priceBg || '#FFCC00';
            const fg = item.priceFg || '#000000';
            c.save();
            c.fillStyle = bg;
            c.fillRect(0, yTop, size, yBottom - yTop);
            // Subtle inner highlight at top edge of the band.
            const hi = c.createLinearGradient(0, yTop, 0, yTop + size * 0.012);
            hi.addColorStop(0, 'rgba(255,255,255,0.3)');
            hi.addColorStop(1, 'rgba(255,255,255,0)');
            c.fillStyle = hi;
            c.fillRect(0, yTop, size, size * 0.012);
            const fontSize = (yBottom - yTop) * 0.55;
            c.fillStyle = fg;
            c.textAlign = 'center';
            c.textBaseline = 'middle';
            c.font = `700 ${fontSize}px ${FONT_STACK}`;
            c.fillText(text, size / 2, (yTop + yBottom) / 2 - size * 0.005);
            c.restore();
        }

        function drawPriceRect(c, size, item, text) {
            const scale = (item.priceSize || 100) / 100;
            const offsetY = ((item.priceY || 0) / 100) * size;
            const baseW = size * 0.78;
            const baseH = size * 0.13;
            const w = baseW * scale;
            const h = baseH * scale;
            const x = (size - w) / 2;
            const baseTop = size * 0.755;
            const y = baseTop + offsetY + (baseH - h) / 2;
            const bg = item.priceBg || '#FFCC00';
            const fg = item.priceFg || '#000000';
            c.save();
            c.fillStyle = 'rgba(0,0,0,0.22)';
            roundRect(c, x, y + size * 0.008, w, h, h * 0.28);
            c.fill();
            c.fillStyle = bg;
            roundRect(c, x, y, w, h, h * 0.28);
            c.fill();
            const fontSize = h * 0.62;
            c.fillStyle = fg;
            c.textAlign = 'center';
            c.textBaseline = 'middle';
            c.font = `700 ${fontSize}px ${FONT_STACK}`;
            c.fillText(text, size / 2, y + h / 2);
            c.restore();
        }

        function hexBrightness(hex) {
            const c = (hex || '').replace('#', '');
            if (c.length !== 6) return 255;
            const r = parseInt(c.substr(0, 2), 16);
            const g = parseInt(c.substr(2, 2), 16);
            const b = parseInt(c.substr(4, 2), 16);
            return (r * 299 + g * 587 + b * 114) / 1000;
        }

        // Map legacy "white"/"black" values to hex so old items still render.
        function normalizeNickColor(v) {
            if (!v) return '#FFFFFF';
            if (v === 'white') return '#FFFFFF';
            if (v === 'black') return '#000000';
            return v;
        }

        function drawNicknameArc(c, size, text, item, position) {
            const color = normalizeNickColor(item.nickColor);
            const sizePct = item.nickSize || 100;
            const edgePct = (item.nickEdge != null) ? item.nickEdge : 14;
            const cx = size / 2;
            const cy = size / 2;
            const R = size / 2;
            const textRadius = R * Math.max(0.55, Math.min(0.99, 1 - edgePct / 100));
            const fontSize = Math.max(8, size * 0.075 * (sizePct / 100));
            c.save();
            c.font = `700 ${fontSize}px ${FONT_STACK}`;
            c.textAlign = 'center';
            c.textBaseline = 'middle';
            const chars = Array.from(text);
            const widths = chars.map(ch => c.measureText(ch).width + size * 0.003);
            const totalW = widths.reduce((a, b) => a + b, 0);
            const totalAngle = totalW / textRadius;

            // Bottom arc: text reads right→left visually (start at upper-right of bottom, sweep counterclockwise).
            // Top arc: text reads left→right (start at upper-left of top, sweep clockwise).
            let startAngle, direction, rotationOffset;
            if (position === 'top') {
                startAngle = -Math.PI / 2 - totalAngle / 2;
                direction = 1;
                rotationOffset = Math.PI / 2;
            } else {
                startAngle = Math.PI / 2 + totalAngle / 2;
                direction = -1;
                rotationOffset = -Math.PI / 2;
            }

            // Pick a contrasting shadow so the text is readable against any background.
            const isDarkText = hexBrightness(color) < 130;
            const shadowColor = isDarkText ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.75)';
            const shadowBlur = size * (isDarkText ? 0.012 : 0.016);

            let cumulative = 0;
            for (let i = 0; i < chars.length; i++) {
                const w = widths[i];
                const a = startAngle + direction * (cumulative + w / 2) / textRadius;
                cumulative += w;
                const x = cx + Math.cos(a) * textRadius;
                const y = cy + Math.sin(a) * textRadius;
                c.save();
                c.translate(x, y);
                c.rotate(a + rotationOffset);
                c.shadowColor = shadowColor;
                c.shadowBlur = shadowBlur;
                if (!isDarkText) c.shadowOffsetY = size * 0.003;
                c.fillStyle = color;
                c.fillText(chars[i], 0, 0);
                c.restore();
            }
            c.restore();
        }

        function drawBadge(c, size, item) {
            const cx = size / 2;
            const cy = size / 2;
            const R = size / 2;
            const ratio = size / CANVAS_DISPLAY_SIZE;

            c.clearRect(0, 0, size, size);
            c.save();
            c.beginPath();
            c.arc(cx, cy, R, 0, Math.PI * 2);
            c.closePath();
            c.clip();

            if (item.image && item.image.complete && item.image.naturalWidth) {
                const img = item.image;
                const baseFit = Math.max(size / img.naturalWidth, size / img.naturalHeight);
                const scale = baseFit * (item.zoom / 100);
                const drawW = img.naturalWidth * scale;
                const drawH = img.naturalHeight * scale;
                const dx = cx - drawW / 2 + item.offsetX * ratio;
                const dy = cy - drawH / 2 + item.offsetY * ratio;
                c.drawImage(img, dx, dy, drawW, drawH);
            } else {
                c.fillStyle = '#eee';
                c.fillRect(0, 0, size, size);
                c.fillStyle = '#999';
                c.textAlign = 'center';
                c.textBaseline = 'middle';
                c.font = `500 ${size * 0.05}px ${FONT_STACK}`;
                c.fillText('Загрузка...', cx, cy);
            }

            const priceText = item.showPrice ? formatPrice(item.priceValue) : '';
            const hasPrice = !!priceText;

            if (hasPrice) {
                if (item.priceStyle === 'rect') {
                    drawPriceRect(c, size, item, priceText);
                } else {
                    drawPriceArc(c, size, item, priceText);
                }
            }

            if (item.showNickname) {
                let nick = (item.nickname || '').trim();
                // "Use as price" formatting: append ₽; if the text is purely digits,
                // also insert thousands separators so it reads as a proper price.
                if (item.nickAsPrice && nick) {
                    const digits = nick.replace(/[^\d]/g, '');
                    if (digits && /^\d+$/.test(nick)) {
                        nick = parseInt(digits, 10).toLocaleString('ru-RU') + ' ₽';
                    } else if (!nick.includes('₽')) {
                        nick = nick + ' ₽';
                    }
                }
                if (nick) {
                    // Position: manual override (top/bottom) or auto (top when price occupies the bottom).
                    let pos;
                    if (item.nickPosition === 'top') pos = 'top';
                    else if (item.nickPosition === 'bottom') pos = 'bottom';
                    else pos = hasPrice ? 'top' : 'bottom';
                    drawNicknameArc(c, size, nick, item, pos);
                }
            }

            c.restore();
        }

        function renderPreview() {
            const item = currentItem();
            if (!item) return;
            drawBadge(ctx, CANVAS_DISPLAY_SIZE, item);
        }

        function exportItemToBlob(item) {
            return new Promise(resolve => {
                const off = document.createElement('canvas');
                off.width = BADGE_SIZE;
                off.height = BADGE_SIZE;
                const offCtx = off.getContext('2d');
                drawBadge(offCtx, BADGE_SIZE, item);
                off.toBlob(blob => resolve({ blob, canvas: off }), 'image/png');
            });
        }

        // ---------- Image loading ----------

        function loadImage(src, useCrossOrigin) {
            return new Promise((resolve, reject) => {
                const img = new Image();
                if (useCrossOrigin) img.crossOrigin = 'anonymous';
                img.onload = () => resolve(img);
                img.onerror = () => reject(new Error('Не удалось загрузить изображение'));
                img.src = src;
            });
        }

        // ---------- Item management ----------

        function currentItem() {
            return badgeState.items.find(it => it.id === badgeState.activeId) || null;
        }

        function makeItem(srcUrl, name, isRemote) {
            return {
                id: 'b_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8),
                src: srcUrl,
                name: name || 'photo',
                isRemote: !!isRemote,
                image: null,
                imageLoading: true,
                imageError: null,
                zoom: 100,
                offsetX: 0,
                offsetY: 0,
                showPrice: false,
                priceValue: '',
                priceStyle: 'arc',
                priceY: 9,          // -50..+20: vertical offset from default position
                priceSize: 60,      // 60..160 (% scale of band height)
                priceBg: '#FFCC00', // ribbon color
                priceFg: '#000000', // digits color
                showNickname: false,
                nickname: '',
                nickColor: '#FFFFFF', // text color (hex)
                nickSize: 100,        // 60..160 (% scale of font)
                nickEdge: 14,         // 0..30: percent inset from circle edge (0 = at edge)
                nickPosition: 'auto', // 'auto' | 'top' | 'bottom'
                nickAsPrice: false,   // append ₽ + format digits as price
            };
        }

        async function addItemFromUrl(srcUrl, name, isRemote) {
            const item = makeItem(srcUrl, name, isRemote);
            badgeState.items.push(item);
            renderItemsGrid();
            try {
                const img = await loadImage(srcUrl, isRemote);
                item.image = img;
                item.imageLoading = false;
                renderItemsGrid();
                if (badgeState.activeId === item.id) renderPreview();
            } catch (err) {
                item.imageLoading = false;
                item.imageError = err.message || 'ошибка';
                renderItemsGrid();
                showNotification(`Не удалось загрузить ${name}`, 'error');
            }
            return item;
        }

        function selectItem(id) {
            const wasHidden = el.editor.classList.contains('hidden');
            badgeState.activeId = id;
            const item = currentItem();
            if (!item) {
                el.editor.classList.add('hidden');
                return;
            }
            el.editor.classList.remove('hidden');
            if (wasHidden && window.innerWidth < 900) {
                setTimeout(() => {
                    el.editor.scrollIntoView({ behavior: 'smooth', block: 'start' });
                }, 60);
            }
            // Populate controls from item state
            el.zoom.value = item.zoom;
            el.zoomValue.textContent = item.zoom + '%';
            el.priceEnabled.checked = item.showPrice;
            el.priceOptions.classList.toggle('hidden', !item.showPrice);
            el.priceValue.value = item.priceValue;
            document.querySelectorAll('input[name="badge-price-style"]').forEach(r => {
                r.checked = r.value === item.priceStyle;
            });
            el.priceY.value = item.priceY;
            el.priceYValue.textContent = item.priceY;
            el.priceSize.value = item.priceSize;
            el.priceSizeValue.textContent = item.priceSize + '%';
            el.priceBg.value = item.priceBg;
            el.priceFg.value = item.priceFg;
            syncSwatches('bg', item.priceBg);
            syncSwatches('fg', item.priceFg);
            el.nickEnabled.checked = item.showNickname;
            el.nickOptions.classList.toggle('hidden', !item.showNickname);
            el.nickValue.value = item.nickname || badgeState.defaultNickname || '';
            const nickHex = normalizeNickColor(item.nickColor);
            item.nickColor = nickHex;
            el.nickColor.value = nickHex;
            syncSwatches('nick', nickHex);
            el.nickSize.value = item.nickSize;
            el.nickSizeValue.textContent = item.nickSize + '%';
            el.nickEdge.value = item.nickEdge;
            el.nickEdgeValue.textContent = item.nickEdge;
            el.nickAsPrice.checked = !!item.nickAsPrice;
            document.querySelectorAll('input[name="badge-nick-position"]').forEach(r => {
                r.checked = r.value === (item.nickPosition || 'auto');
            });
            renderItemsGrid();
            renderPreview();
        }

        function removeItem(id) {
            const idx = badgeState.items.findIndex(it => it.id === id);
            if (idx === -1) return;
            badgeState.items.splice(idx, 1);
            if (badgeState.activeId === id) {
                const next = badgeState.items[0];
                if (next) {
                    selectItem(next.id);
                } else {
                    badgeState.activeId = null;
                    el.editor.classList.add('hidden');
                }
            }
            renderItemsGrid();
        }

        function renderItemsGrid() {
            const grid = el.itemsGrid;
            if (!grid) return;
            el.itemsCount.textContent = String(badgeState.items.length);
            el.batchActions.classList.toggle('hidden', badgeState.items.length < 2);

            if (badgeState.items.length === 0) {
                grid.innerHTML = '<p class="placeholder">Выберите фото из галереи или загрузите с устройства</p>';
                return;
            }

            grid.innerHTML = '';
            badgeState.items.forEach(item => {
                const div = document.createElement('div');
                div.className = 'badge-item' + (item.id === badgeState.activeId ? ' active' : '');
                if (item.showPrice || item.showNickname) div.classList.add('has-extras');
                div.dataset.id = item.id;

                const img = document.createElement('img');
                img.className = 'badge-item-preview';
                img.alt = item.name;
                img.src = item.src;
                if (item.isRemote) img.crossOrigin = 'anonymous';
                div.appendChild(img);

                const remove = document.createElement('button');
                remove.type = 'button';
                remove.className = 'badge-item-remove';
                remove.title = 'Убрать';
                remove.textContent = '×';
                remove.addEventListener('click', (e) => {
                    e.stopPropagation();
                    removeItem(item.id);
                });
                div.appendChild(remove);

                const dot = document.createElement('span');
                dot.className = 'badge-item-badge-dot';
                dot.title = 'С декором (цена/никнейм)';
                div.appendChild(dot);

                div.addEventListener('click', () => selectItem(item.id));
                grid.appendChild(div);
            });
        }

        // ---------- Drag interactions ----------

        let dragging = false;
        let dragStart = null;
        let pinchStart = null;

        function pointerPosOnCanvas(e) {
            const rect = el.canvas.getBoundingClientRect();
            const t = (e.touches && e.touches[0]) ? e.touches[0] : e;
            return {
                x: (t.clientX - rect.left) * (CANVAS_DISPLAY_SIZE / rect.width),
                y: (t.clientY - rect.top) * (CANVAS_DISPLAY_SIZE / rect.height),
            };
        }

        function touchDistance(touches) {
            const dx = touches[0].clientX - touches[1].clientX;
            const dy = touches[0].clientY - touches[1].clientY;
            return Math.hypot(dx, dy);
        }

        function setZoom(item, value) {
            const next = Math.max(100, Math.min(400, Math.round(value)));
            item.zoom = next;
            el.zoom.value = next;
            el.zoomValue.textContent = next + '%';
        }

        function onPointerDown(e) {
            const item = currentItem();
            if (!item) return;
            e.preventDefault();
            dragging = true;
            const p = pointerPosOnCanvas(e);
            dragStart = { px: p.x, py: p.y, ox: item.offsetX, oy: item.offsetY };
        }

        function onPointerMove(e) {
            if (!dragging || !dragStart) return;
            const item = currentItem();
            if (!item) return;
            e.preventDefault();
            const p = pointerPosOnCanvas(e);
            item.offsetX = dragStart.ox + (p.x - dragStart.px);
            item.offsetY = dragStart.oy + (p.y - dragStart.py);
            renderPreview();
        }

        function onPointerUp() {
            dragging = false;
            dragStart = null;
        }

        function onCanvasTouchStart(e) {
            const item = currentItem();
            if (!item) return;
            if (e.touches.length >= 2) {
                e.preventDefault();
                dragging = false;
                dragStart = null;
                pinchStart = { dist: touchDistance(e.touches), zoom: item.zoom };
                return;
            }
            onPointerDown(e);
        }

        function onCanvasTouchMove(e) {
            const item = currentItem();
            if (!item) return;
            if (e.touches.length >= 2 && pinchStart) {
                e.preventDefault();
                const dist = touchDistance(e.touches);
                if (pinchStart.dist > 0) {
                    setZoom(item, pinchStart.zoom * (dist / pinchStart.dist));
                    renderPreview();
                }
                return;
            }
            onPointerMove(e);
        }

        function onCanvasTouchEnd(e) {
            if (e.touches.length < 2) pinchStart = null;
            if (e.touches.length === 0) onPointerUp();
        }

        el.canvas.addEventListener('mousedown', onPointerDown);
        window.addEventListener('mousemove', onPointerMove);
        window.addEventListener('mouseup', onPointerUp);
        el.canvas.addEventListener('touchstart', onCanvasTouchStart, { passive: false });
        el.canvas.addEventListener('touchmove', onCanvasTouchMove, { passive: false });
        el.canvas.addEventListener('touchend', onCanvasTouchEnd);
        el.canvas.addEventListener('touchcancel', onCanvasTouchEnd);
        el.canvas.addEventListener('wheel', (e) => {
            const item = currentItem();
            if (!item) return;
            e.preventDefault();
            const delta = e.deltaY > 0 ? -5 : 5;
            setZoom(item, item.zoom + delta);
            renderPreview();
        }, { passive: false });

        // ---------- Controls ----------

        el.zoom.addEventListener('input', () => {
            const item = currentItem();
            if (!item) return;
            const v = parseInt(el.zoom.value, 10) || 100;
            item.zoom = v;
            el.zoomValue.textContent = v + '%';
            renderPreview();
        });

        el.btnReset.addEventListener('click', () => {
            const item = currentItem();
            if (!item) return;
            item.zoom = 100;
            item.offsetX = 0;
            item.offsetY = 0;
            el.zoom.value = 100;
            el.zoomValue.textContent = '100%';
            renderPreview();
        });

        el.priceEnabled.addEventListener('change', () => {
            const item = currentItem();
            if (!item) return;
            item.showPrice = el.priceEnabled.checked;
            el.priceOptions.classList.toggle('hidden', !item.showPrice);
            renderPreview();
            renderItemsGrid();
        });

        el.priceValue.addEventListener('input', () => {
            const item = currentItem();
            if (!item) return;
            item.priceValue = el.priceValue.value;
            renderPreview();
        });

        document.querySelectorAll('input[name="badge-price-style"]').forEach(r => {
            r.addEventListener('change', () => {
                const item = currentItem();
                if (!item) return;
                if (r.checked) {
                    item.priceStyle = r.value;
                    renderPreview();
                }
            });
        });

        // Price vertical position slider
        el.priceY.addEventListener('input', () => {
            const item = currentItem();
            if (!item) return;
            const v = parseInt(el.priceY.value, 10) || 0;
            item.priceY = v;
            el.priceYValue.textContent = v;
            renderPreview();
        });

        // Price size slider
        el.priceSize.addEventListener('input', () => {
            const item = currentItem();
            if (!item) return;
            const v = parseInt(el.priceSize.value, 10) || 100;
            item.priceSize = v;
            el.priceSizeValue.textContent = v + '%';
            renderPreview();
        });

        // Color: ribbon background
        function normalizeHex(v) {
            return (v || '').toString().toLowerCase();
        }

        function syncSwatches(target, color) {
            const norm = normalizeHex(color);
            document.querySelectorAll(`.badge-color-row[data-target="${target}"] .badge-color-swatch`).forEach(sw => {
                sw.classList.toggle('active', normalizeHex(sw.dataset.color) === norm);
            });
        }

        el.priceBg.addEventListener('input', () => {
            const item = currentItem();
            if (!item) return;
            item.priceBg = el.priceBg.value;
            syncSwatches('bg', item.priceBg);
            renderPreview();
        });

        el.priceFg.addEventListener('input', () => {
            const item = currentItem();
            if (!item) return;
            item.priceFg = el.priceFg.value;
            syncSwatches('fg', item.priceFg);
            renderPreview();
        });

        document.querySelectorAll('.badge-color-row .badge-color-swatch').forEach(sw => {
            sw.addEventListener('click', () => {
                const item = currentItem();
                if (!item) return;
                const target = sw.parentElement.dataset.target;
                const color = sw.dataset.color;
                if (target === 'bg') {
                    item.priceBg = color;
                    el.priceBg.value = color;
                } else if (target === 'fg') {
                    item.priceFg = color;
                    el.priceFg.value = color;
                } else if (target === 'nick') {
                    item.nickColor = color;
                    el.nickColor.value = color;
                }
                syncSwatches(target, color);
                renderPreview();
            });
        });

        el.nickEnabled.addEventListener('change', () => {
            const item = currentItem();
            if (!item) return;
            item.showNickname = el.nickEnabled.checked;
            el.nickOptions.classList.toggle('hidden', !item.showNickname);
            if (item.showNickname && !item.nickname && badgeState.defaultNickname) {
                item.nickname = badgeState.defaultNickname;
                el.nickValue.value = badgeState.defaultNickname;
            }
            renderPreview();
            renderItemsGrid();
        });

        el.nickValue.addEventListener('input', () => {
            const item = currentItem();
            if (!item) return;
            item.nickname = el.nickValue.value;
            renderPreview();
        });

        // Nickname color (hex input + swatches handled together with price swatches)
        el.nickColor.addEventListener('input', () => {
            const item = currentItem();
            if (!item) return;
            item.nickColor = el.nickColor.value;
            syncSwatches('nick', item.nickColor);
            renderPreview();
        });

        // Nickname size
        el.nickSize.addEventListener('input', () => {
            const item = currentItem();
            if (!item) return;
            const v = parseInt(el.nickSize.value, 10) || 100;
            item.nickSize = v;
            el.nickSizeValue.textContent = v + '%';
            renderPreview();
        });

        // Nickname edge offset
        el.nickEdge.addEventListener('input', () => {
            const item = currentItem();
            if (!item) return;
            const v = parseInt(el.nickEdge.value, 10) || 0;
            item.nickEdge = v;
            el.nickEdgeValue.textContent = v;
            renderPreview();
        });

        // Nickname "use as price" — append ₽ to the rendered text.
        el.nickAsPrice.addEventListener('change', () => {
            const item = currentItem();
            if (!item) return;
            item.nickAsPrice = el.nickAsPrice.checked;
            renderPreview();
        });

        // Nickname position (auto / top / bottom).
        document.querySelectorAll('input[name="badge-nick-position"]').forEach(r => {
            r.addEventListener('change', () => {
                const item = currentItem();
                if (!item) return;
                if (r.checked) {
                    item.nickPosition = r.value;
                    renderPreview();
                }
            });
        });

        el.btnRemove.addEventListener('click', () => {
            const item = currentItem();
            if (item) removeItem(item.id);
        });

        // ---------- File upload ----------

        el.btnUpload.addEventListener('click', () => el.fileInput.click());

        el.fileInput.addEventListener('change', (e) => {
            const files = Array.from(e.target.files || []);
            if (!files.length) return;
            files.forEach(file => {
                if (!file.type.startsWith('image/')) {
                    showNotification(`${file.name}: не изображение`, 'error');
                    return;
                }
                if (file.size > 25 * 1024 * 1024) {
                    showNotification(`${file.name}: слишком большой (макс 25MB)`, 'error');
                    return;
                }
                const reader = new FileReader();
                reader.onload = async (ev) => {
                    const item = await addItemFromUrl(ev.target.result, file.name, false);
                    if (!badgeState.activeId) selectItem(item.id);
                };
                reader.readAsDataURL(file);
            });
            e.target.value = '';
        });

        // ---------- Flickr selection ----------

        el.btnFromFlickr.addEventListener('click', async () => {
            const photos = state.selectedPhotos || [];
            if (photos.length === 0) {
                showNotification('Сначала выберите фото в Галерее (галочками)', 'info');
                document.querySelector('.nav-btn[data-tab="gallery"]')?.click();
                return;
            }
            const added = await autoImportGallerySelection(true);
            if (added === 0) {
                showNotification('Все выбранные фото уже в списке', 'info');
            }
        });

        // ---------- Download / save ----------

        function triggerDownload(blob, filename) {
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            a.remove();
            setTimeout(() => URL.revokeObjectURL(url), 1000);
        }

        // On phones, prefer the OS share sheet ("Save to Photos" on iOS, "Save image" on Android).
        // Falls back to a regular download elsewhere.
        async function saveOrShare(blob, filename) {
            try {
                if (typeof File !== 'undefined' && navigator.canShare) {
                    const file = new File([blob], filename, { type: 'image/png' });
                    if (navigator.canShare({ files: [file] })) {
                        await navigator.share({ files: [file], title: 'Цифровой бейдж' });
                        return 'shared';
                    }
                }
            } catch (err) {
                if (err && err.name === 'AbortError') return 'aborted';
                // fall through to download
            }
            triggerDownload(blob, filename);
            return 'downloaded';
        }

        function badgeFilename(item, idx) {
            const safeName = (item.name || 'badge')
                .replace(/\.[^.]+$/, '')
                .replace(/[^\wа-яА-ЯёЁ\-_.]+/g, '_')
                .slice(0, 32) || 'badge';
            return `badge_${safeName}${idx != null ? '_' + (idx + 1) : ''}.png`;
        }

        async function saveToServerHistory(canvas, item) {
            try {
                const dataUrl = canvas.toDataURL('image/png');
                const fd = new FormData();
                fd.append('action', 'badge_save');
                fd.append('image', dataUrl);
                fd.append('has_price', item.showPrice ? '1' : '0');
                fd.append('has_nickname', item.showNickname ? '1' : '0');
                fd.append('label', item.name || '');
                const res = await fetch('api.php', { method: 'POST', body: fd });
                const data = await res.json();
                if (data.error) {
                    console.warn('Не удалось сохранить в историю:', data.error);
                }
                return data;
            } catch (err) {
                console.warn('История недоступна:', err);
            }
        }

        el.btnDownload.addEventListener('click', async () => {
            const item = currentItem();
            if (!item) return;
            if (!item.image) {
                showNotification('Фото ещё не загружено', 'error');
                return;
            }
            try {
                const { blob, canvas } = await exportItemToBlob(item);
                if (!blob) {
                    showNotification('Не удалось сохранить (CORS?)', 'error');
                    return;
                }
                const result = await saveOrShare(blob, badgeFilename(item));
                await saveToServerHistory(canvas, item);
                loadHistory();
                if (result === 'shared') {
                    showNotification('Готово — сохраните в «Фото»', 'success');
                } else if (result === 'downloaded') {
                    showNotification('Файл скачан', 'success');
                }
            } catch (err) {
                showNotification('Ошибка: ' + (err.message || err), 'error');
            }
        });

        el.btnDownloadAll.addEventListener('click', async () => {
            if (badgeState.items.length === 0) return;
            const ready = badgeState.items.filter(it => it.image);
            if (ready.length === 0) {
                showNotification('Нет загруженных фото', 'error');
                return;
            }
            showNotification(`Сохраняю ${ready.length} бейджей...`, 'info');
            for (let i = 0; i < ready.length; i++) {
                const item = ready[i];
                try {
                    const { blob, canvas } = await exportItemToBlob(item);
                    if (!blob) continue;
                    triggerDownload(blob, badgeFilename(item, i));
                    await saveToServerHistory(canvas, item);
                    await new Promise(r => setTimeout(r, 250));
                } catch (err) {
                    console.warn('Skip item:', err);
                }
            }
            loadHistory();
        });

        // ---------- History ----------

        async function loadHistory() {
            if (!el.historyGrid) return;
            try {
                const fd = new FormData();
                fd.append('action', 'badge_list');
                const res = await fetch('api.php', { method: 'POST', body: fd });
                const data = await res.json();
                renderHistory(data.items || []);
            } catch (err) {
                console.warn('Не удалось загрузить историю:', err);
            }
        }

        function renderHistory(items) {
            if (!items.length) {
                el.historyGrid.innerHTML = '<p class="placeholder">История пуста — сгенерированные бейджи будут появляться здесь.</p>';
                return;
            }
            el.historyGrid.innerHTML = '';
            items.forEach(it => {
                const div = document.createElement('div');
                div.className = 'badge-history-item';

                const date = document.createElement('span');
                date.className = 'badge-history-date';
                date.textContent = (it.created_at || '').slice(5, 16).replace(' ', ' ');
                div.appendChild(date);

                const img = document.createElement('img');
                img.src = it.url;
                img.alt = it.filename;
                div.appendChild(img);

                const actions = document.createElement('div');
                actions.className = 'badge-history-actions';
                const dl = document.createElement('button');
                dl.type = 'button';
                dl.textContent = 'Сохранить';
                dl.addEventListener('click', async () => {
                    try {
                        const res = await fetch(it.url);
                        const blob = await res.blob();
                        await saveOrShare(blob, it.filename);
                    } catch (err) {
                        const a = document.createElement('a');
                        a.href = it.url;
                        a.download = it.filename;
                        document.body.appendChild(a);
                        a.click();
                        a.remove();
                    }
                });
                actions.appendChild(dl);

                const rm = document.createElement('button');
                rm.type = 'button';
                rm.className = 'danger';
                rm.textContent = 'Удалить';
                rm.addEventListener('click', async () => {
                    if (!confirm('Удалить этот бейдж из истории?')) return;
                    const fd = new FormData();
                    fd.append('action', 'badge_delete');
                    fd.append('filename', it.filename);
                    await fetch('api.php', { method: 'POST', body: fd });
                    loadHistory();
                });
                actions.appendChild(rm);
                div.appendChild(actions);

                el.historyGrid.appendChild(div);
            });
        }

        // ---------- Settings (nickname) ----------

        async function loadBadgeSettings() {
            try {
                const fd = new FormData();
                fd.append('action', 'get_badge_settings');
                const res = await fetch('api.php', { method: 'POST', body: fd });
                const data = await res.json();
                const nick = (data.settings && data.settings.nickname) || '';
                badgeState.defaultNickname = nick;
                if (el.nickDefault) el.nickDefault.value = nick;
            } catch (err) {
                console.warn('Badge settings load failed:', err);
            }
        }

        if (el.btnSaveNick) {
            el.btnSaveNick.addEventListener('click', async () => {
                const nick = el.nickDefault.value.trim();
                el.nickSaveStatus.textContent = 'Сохранение...';
                try {
                    const fd = new FormData();
                    fd.append('action', 'save_badge_settings');
                    fd.append('nickname', nick);
                    const res = await fetch('api.php', { method: 'POST', body: fd });
                    const data = await res.json();
                    if (data.success) {
                        badgeState.defaultNickname = nick;
                        el.nickSaveStatus.textContent = '✓ Сохранено';
                        el.nickSaveStatus.style.color = '#34C759';
                        setTimeout(() => { el.nickSaveStatus.textContent = ''; }, 2000);
                    } else {
                        el.nickSaveStatus.textContent = 'Ошибка: ' + (data.error || '');
                        el.nickSaveStatus.style.color = '#FF3B30';
                    }
                } catch (err) {
                    el.nickSaveStatus.textContent = 'Ошибка сети';
                    el.nickSaveStatus.style.color = '#FF3B30';
                }
            });
        }

        // ---------- Tab activation ----------

        // Pull in currently-selected gallery photos that aren't already in the
        // badge list. Called when opening the badge tab and when clicking the
        // "from Flickr" button. Returns the number of items added.
        async function autoImportGallerySelection(showFeedback) {
            const photos = state.selectedPhotos || [];
            if (photos.length === 0) return 0;
            const existingSrcs = new Set(badgeState.items.map(it => it.src));
            const toAdd = [];
            for (const photo of photos) {
                const url = (photo.urls && (photo.urls.large || photo.urls.medium640 || photo.urls.medium)) || null;
                if (!url || existingSrcs.has(url)) continue;
                toAdd.push({ url, name: photo.title || ('photo_' + photo.id) });
            }
            if (toAdd.length === 0) return 0;
            for (const t of toAdd) {
                const item = await addItemFromUrl(t.url, t.name, true);
                if (!badgeState.activeId) selectItem(item.id);
            }
            if (showFeedback) {
                showNotification(`Добавлено ${toAdd.length} фото из Галереи`, 'success');
            }
            return toAdd.length;
        }

        document.querySelectorAll('.nav-btn[data-tab="badge"]').forEach(btn => {
            btn.addEventListener('click', () => {
                loadBadgeSettings();
                loadHistory();
                // Auto-import gallery selections so users see them right away
                // without having to click "Выбранные в галерее Flickr".
                autoImportGallerySelection(false);
            });
        });

        // Initial settings load (so default nickname is available even before opening the tab)
        loadBadgeSettings();
    }
});
