/* ══════════════════════════════════════════════════════════════════
   TelePush — Main Application JavaScript
   ══════════════════════════════════════════════════════════════════ */

// ── Flag Helper ────────────────────────────────────────────────
// Emoji-first: рендерим Unicode regional indicator (🇷🇺/🇺🇦/🇺🇿). Современный
// WebView2 (Edge Chromium 100+) рендерит их нативно через сегментированный шрифт.
// Если по какой-то причине emoji-рендера нет — JS не имеет надёжного способа
// проверить и fallback на img делается через CSS @supports (или ничего).
// Это убирает запросы к flagcdn.com (раньше каждый таб дёргал N картинок).
function flagEmoji(cc) {
    if (!cc || cc.length !== 2 || cc === 'xx' || cc === 'XX') return '';
    try {
        var c = cc.toUpperCase();
        return String.fromCodePoint(0x1F1E6 + c.charCodeAt(0) - 65, 0x1F1E6 + c.charCodeAt(1) - 65);
    } catch (e) { return ''; }
}
function flagImg(cc) {
    if (!cc || cc === 'xx') return '';
    var emoji = flagEmoji(cc);
    var title = cc.toUpperCase();
    if (!emoji) return '<span class="cc-text" title="' + title + '" style="font-size:0.65rem;color:var(--text-muted);">' + title + '</span>';
    // Emoji первичный. <img> — fallback через onerror только если emoji-шрифт
    // действительно отсутствует (на новых WebView2 это не срабатывает, что и нужно).
    return '<span class="flag-emoji" title="' + title + '" style="font-size:1rem;line-height:1;vertical-align:middle;">' + emoji + '</span>';
}

// ── Avatar cache bust (set once on load, reset on profile update) ──
var _avatarCacheBust = Date.now();

// ── API Helper ──────────────────────────────────────────────────

var API = {
    post: function(url, data) {
        return fetch(url, {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(data || {})
        }).then(function(r) {
            if (!r.ok) return r.json().catch(function() { return {}; }).then(function(d) {
                // Throw Error с человеческим .message чтобы toString давал
                // понятный текст. Раньше кидали сырой объект → клиент видел
                // «Ошибка: [object Object]» вместо реальной причины.
                var msg = (d && (d.error || d.message)) || ('HTTP ' + r.status);
                var err = new Error(msg);
                err.data = d || {};
                err.status = r.status;
                throw err;
            });
            return r.json();
        });
    },
    get: function(url) {
        return fetch(url).then(function(r) {
            if (!r.ok) throw new Error('HTTP ' + r.status);
            return r.json();
        });
    }
};

// ── Tab Navigation ──────────────────────────────────────────────

function showTab(name) {
    document.querySelectorAll('.section').forEach(function(s) {
        s.classList.remove('active');
    });
    var sec = document.getElementById('tab-' + name);
    if (sec) sec.classList.add('active');

    // Module lock check
    var moduleMap = {'spam':'spam','dm':'dm','join':'join','multi':'multi','accounts':'accounts','proxy':'proxy','parser':'parser','warmup':'warmup','profiles':'profiles','autoreply':'autoreply','templates':'templates','analytics':'analytics'};
    var module = moduleMap[name];
    if (module && _allowedModules.length && _allowedModules.indexOf(module) === -1) {
        _showModuleBlocked(name);
    }
    document.querySelectorAll('.nav-link').forEach(function(l) {
        l.classList.remove('active');
    });
    var link = document.querySelector('.nav-link[data-tab="' + name + '"]');
    if (link) link.classList.add('active');
    // Auto-load data for tab
    if (name === 'accounts') {
        // При открытии вкладки сначала синкаем + сканируем папку (вдруг
        // клиент закинул новые .session/.json в проводнике), потом грузим.
        // Тихо — без всплывающих toast'ов, как фоновое обновление.
        API.post('/api/accounts/sync')
            .then(function() { return API.post('/api/accounts/scan_folder'); })
            .then(loadAccounts)
            .catch(loadAccounts);
        // Периодический авто-скан пока вкладка открыта (раз в 8 сек).
        if (window._accountsAutoScanTimer) clearInterval(window._accountsAutoScanTimer);
        window._accountsAutoScanTimer = setInterval(function() {
            if (_currentTab !== 'accounts') {
                clearInterval(window._accountsAutoScanTimer);
                window._accountsAutoScanTimer = null;
                return;
            }
            API.post('/api/accounts/scan_folder').then(function(d) {
                if (d && d.added > 0) loadAccounts();
            }).catch(function() {});
        }, 8000);
    }
    if (name === 'analytics') { loadAnalytics(); if (typeof loadAdStats === 'function') loadAdStats(); }
    if (name === 'templates') {
        loadTemplates('all');
        if (window._templatesAutoTimer) clearInterval(window._templatesAutoTimer);
        window._templatesAutoTimer = setInterval(function() {
            if (_currentTab !== 'templates') {
                clearInterval(window._templatesAutoTimer);
                window._templatesAutoTimer = null;
                return;
            }
            loadTemplates('all');
        }, 10000);
    }
    if (name === 'proxy') {
        loadProxies();
        if (window._proxyAutoTimer) clearInterval(window._proxyAutoTimer);
        window._proxyAutoTimer = setInterval(function() {
            if (_currentTab !== 'proxy') {
                clearInterval(window._proxyAutoTimer);
                window._proxyAutoTimer = null;
                return;
            }
            loadProxies();
        }, 10000);
    }
    if (name === 'multi') { loadMultiAccounts(); refreshMultiTimings(); }
    if (name === 'settings') loadSettings();
    if (name === 'parser') loadParserChats();
    if (name === 'warmup') loadWarmup();
    if (name === 'profile') loadProfile();
    if (['spam', 'dm', 'join', 'parser', 'profiles', 'autoreply', 'imitation'].indexOf(name) !== -1) renderAccountSelectors();
    if (name === 'imitation') { loadImitationStatus(); if (window._imitTimer) clearInterval(window._imitTimer); window._imitTimer = setInterval(loadImitationStatus, 5000); }
    if (name === 'boost') { onBoostTypeChange(); pollBoostStatus(); }
    if (name === 'checker') { loadCheckerConfig(); pollCheckerStatus(); renderAccountSelectors(); }
    if (name === 'reauth') loadReauth();
    // Конвертеры подгружают acc-список такой же как reauth, так как пикер общий
    if (name === 'conv-t2s' || name === 'conv-s2t') {
        if (typeof loadReauth === 'function') loadReauth();
    }
    if (name === 'monitor') { loadMonitor(); renderAccountSelectors(); }
    if (name === 'invite') { loadInviteSettings(); refreshInviteStatus(); }
    if (name === 'history') loadHistory();
    if (name === 'dashboard') loadDashboard();
}

// ── Главная (Dashboard) ──────────────────────────────────────
var _dashInterval = null;
function loadDashboard() {
    API.get('/api/dashboard').then(function(d) {
        if (!d) return;
        var a = d.accounts || {};
        document.getElementById('dash-acc-total').textContent = a.total || 0;
        document.getElementById('dash-acc-ok').textContent = (a.ok || 0) + ' живых';
        document.getElementById('dash-acc-fw').textContent = (a.floodwait || 0) + ' в ожидании';
        document.getElementById('dash-acc-dead').textContent = (a.dead || 0) + ' мёртвых';

        var j = d.jobs || {};
        document.getElementById('dash-jobs-count').textContent = j.total || 0;
        var parts = [];
        if (j.spam) parts.push(j.spam + ' спам');
        if (j.dm) parts.push(j.dm + ' ЛС');
        if (j.join) parts.push(j.join + ' подписки');
        if (j.monitor) parts.push(j.monitor + ' монитор');
        if (j.warmup) parts.push(j.warmup + ' прогрев');
        document.getElementById('dash-jobs-breakdown').textContent = parts.join(' · ') || 'нет задач';

        var t = d.today || {};
        var total = (t.joins || 0) + (t.chat_sends || 0) + (t.dm_sends || 0);
        document.getElementById('dash-today-total').textContent = total;
        document.getElementById('dash-today-joins').textContent = (t.joins || 0) + ' подписок';
        document.getElementById('dash-today-msgs').textContent = ((t.chat_sends || 0) + (t.dm_sends || 0)) + ' сообщений';

        var ev = d.events || [];
        var evHtml = ev.map(function(e) {
            var color = e.level === 'ERROR' ? 'var(--danger)' :
                        e.level === 'WARNING' ? 'var(--warning)' : 'var(--text-muted)';
            return '<div><span style="color:'+color+';font-weight:600">['+e.time+']</span> '+esc(e.message)+'</div>';
        }).join('');
        document.getElementById('dash-events').innerHTML = evHtml || '<div style="color:var(--text-muted)">Событий нет</div>';
    });
    if (!_dashInterval) _dashInterval = setInterval(function() {
        if (_currentTab === 'dashboard') loadDashboard();
    }, 5000);
}

// ── История запусков ───────────────────────────────────────────
function loadHistory() {
    API.get('/api/history').then(function(d) {
        var runs = (d && d.runs) || [];
        var cnt = document.getElementById('history-count');
        if (cnt) cnt.textContent = runs.length ? (runs.length + ' записей') : 'История пуста — запустите задачу';
        var tbl = document.getElementById('history-table');
        if (!tbl) return;
        if (!runs.length) { tbl.innerHTML = '<div class="text-muted text-sm">Нет запусков.</div>'; return; }
        var kindLbl = {join:'Подписка', spam:'Рассылка', dm:'DM', parser:'Парсер', warmup:'Прогрев'};
        var rowsHtml = runs.map(function(r) {
            var d1 = new Date((r.finished_at || 0) * 1000);
            var dur = Math.max(0, (r.finished_at || 0) - (r.started_at || 0));
            var durS = dur < 60 ? Math.round(dur) + 'с' : Math.round(dur/60) + 'мин';
            var statusCol = r.status === 'finished' ? 'var(--success)' :
                            r.status === 'stopped' ? 'var(--warning)' : 'var(--danger)';
            var okBadge = r.ok ? '<span style="color:var(--success);">✓' + r.ok + '</span>' : '';
            var failBadge = r.fail ? '<span style="color:var(--danger);margin-left:8px;">✗' + r.fail + '</span>' : '';
            return '<tr style="border-top:1px solid #1e2433;">' +
                '<td style="padding:6px 8px;">' + d1.toLocaleString() + '</td>' +
                '<td style="padding:6px 8px;"><b>' + (kindLbl[r.kind] || r.kind) + '</b></td>' +
                '<td style="padding:6px 8px;color:var(--text-muted);">+' + (r.phone || '') + '</td>' +
                '<td style="padding:6px 8px;">' + (r.total || 0) + '</td>' +
                '<td style="padding:6px 8px;">' + okBadge + failBadge + '</td>' +
                '<td style="padding:6px 8px;color:' + statusCol + ';">' + (r.status || '') + '</td>' +
                '<td style="padding:6px 8px;color:var(--text-muted);">' + durS + '</td>' +
            '</tr>';
        }).join('');
        tbl.innerHTML = '<table style="width:100%;border-collapse:collapse;">' +
            '<thead><tr style="color:var(--text-muted);text-align:left;font-size:0.72rem;">' +
            '<th style="padding:6px 8px;">Время</th><th style="padding:6px 8px;">Тип</th>' +
            '<th style="padding:6px 8px;">Акк</th><th style="padding:6px 8px;">Всего</th>' +
            '<th style="padding:6px 8px;">✓/✗</th><th style="padding:6px 8px;">Статус</th>' +
            '<th style="padding:6px 8px;">Длит.</th></tr></thead><tbody>' + rowsHtml + '</tbody></table>';
    }).catch(function() {});
}

// ── Отчёт по прогреву ─────────────────────────────────────────
function loadWarmupReport() {
    API.get('/api/warmup/report').then(function(d) {
        var box = document.getElementById('warmup-report');
        if (box) box.style.display = 'block';
        var s = d.summary || {};
        var sumEl = document.getElementById('warmup-report-summary');
        if (sumEl) {
            var parts = ['Аккаунтов: ' + (s.total || 0)];
            if (s.day) parts.push('День: ' + s.day + '/' + (s.total_days || '?'));
            if (s.total_messages !== undefined) parts.push('Сообщений всего: ' + s.total_messages);
            if (s.status) parts.push('Статус: ' + s.status);
            if (s.dead) parts.push('<span style="color:var(--danger);"><i class="bi bi-x-circle-fill"></i> мёртвых: ' + s.dead + '</span>');
            if (s.frozen) parts.push('<span style="color:#5db7ff;"><i class="bi bi-snow"></i> заморожено: ' + s.frozen + '</span>');
            if (s.floodwait) parts.push('<span style="color:var(--warning);"><i class="bi bi-hourglass-split"></i> флуд: ' + s.floodwait + '</span>');
            if (s.spam_block) parts.push('<span style="color:var(--warning);"><i class="bi bi-slash-circle-fill"></i> спам-блок: ' + s.spam_block + '</span>');
            sumEl.innerHTML = parts.join('  •  ');
        }
        var rows = d.accounts || [];
        var tbl = document.getElementById('warmup-report-table');
        if (!tbl) return;
        if (!rows.length) { tbl.innerHTML = '<div class="text-muted text-sm">Нет аккаунтов в папке «Прогрев».</div>'; return; }
        var html = rows.map(function(r) {
            var status = [];
            if (r.dead) status.push('<span style="color:var(--danger);" title="Аккаунт мёртв — сессия отозвана"><i class="bi bi-x-circle-fill"></i> мёртвый</span>');
            if (r.frozen) status.push('<span style="color:#5db7ff;" title="Заморожен Telegram — отлёжка 24+ часов"><i class="bi bi-snow"></i> заморожен</span>');
            if (r.disabled) status.push('<span style="color:var(--warning);" title="Отключён системой' + (r.disabled_reason?' — '+r.disabled_reason:'') + '"><i class="bi bi-shield-exclamation"></i> отключён' + (r.disabled_reason?' ('+r.disabled_reason+')':'') + '</span>');
            if (r.floodwait_left > 0) status.push('<span style="color:var(--warning);" title="Временный флуд-блок от Telegram"><i class="bi bi-hourglass-split"></i> флуд ' + r.floodwait_left + 'с</span>');
            if (r.spam_blocked) status.push('<span style="color:var(--warning);" title="Спам-блок: ' + (r.spam_block_note||'проверить /start @SpamBot') + '"><i class="bi bi-slash-circle-fill"></i> спам-блок</span>');
            if (!status.length) status.push('<span style="color:var(--success);" title="Аккаунт работает нормально"><i class="bi bi-check-circle-fill"></i> живой</span>');
            if (r.idle) status.push('<span style="color:var(--text-muted-strong);" title="Аккаунт в режиме сна"><i class="bi bi-moon-fill"></i> спит</span>');
            return '<tr style="border-top:1px solid #1e2433;">' +
                '<td style="padding:5px 8px;">+' + r.phone + '</td>' +
                '<td style="padding:5px 8px;color:var(--text-muted);">' + (r.username ? '@'+r.username : '—') + '</td>' +
                '<td style="padding:5px 8px;">' + status.join('&nbsp;') + '</td>' +
            '</tr>';
        }).join('');
        tbl.innerHTML = '<table style="width:100%;border-collapse:collapse;">' +
            '<thead><tr style="color:var(--text-muted);text-align:left;font-size:0.7rem;">' +
            '<th style="padding:5px 8px;">Телефон</th><th style="padding:5px 8px;">Username</th>' +
            '<th style="padding:5px 8px;">Статус</th></tr></thead><tbody>' + html + '</tbody></table>';
    }).catch(function() {});
}

// ── Монитор (реал-тайм наблюдение за чатами) ────────────────────────
var _monitorPollTimer = null;

function loadMonitor() {
    API.get('/api/monitor/config').then(function(d) {
        var ch = document.getElementById('monitor-chats');
        var kw = document.getElementById('monitor-keywords');
        var iw = document.getElementById('monitor-ignore-words');
        var tg = document.getElementById('monitor-target');
        var as = document.getElementById('monitor-autostart');
        var adm = document.getElementById('monitor-auto-dm');
        var bcid = document.getElementById('monitor-bot-chat-id');
        var btSaved = document.getElementById('monitor-bot-token-saved');
        var bt = document.getElementById('monitor-bot-token');
        // Подтягиваем сохранённый конфиг ВСЕГДА (раньше было `if (d.chats)` —
        // при пустом ответе textarea оставалась пустой даже если монитор
        // работал с 35 чатами из autoload. Клиент видел пустое поле и думал
        // что окно не работает).
        if (ch && typeof d.chats === 'string') ch.value = d.chats;
        if (kw && typeof d.keywords === 'string') kw.value = d.keywords;
        if (iw && typeof d.ignore_words === 'string') iw.value = d.ignore_words;
        if (tg && d.target) tg.value = d.target;
        var ep = document.getElementById('monitor-extra-phones');
        var mm = document.getElementById('monitor-multi-mode');
        // Подтягиваем сохранённые "доп акки" — это все phones кроме первого
        // (первый — основной из acc-selector). Чтобы UI не показывал
        // его дважды.
        if (ep && Array.isArray(d.phones) && d.phones.length > 1) {
            ep.value = d.phones.slice(1).join('\n');
        }
        if (mm && d.multi_mode) mm.value = d.multi_mode;
        if (as) as.checked = !!d.autostart;
        if (adm && typeof d.auto_dm_text === 'string') adm.value = d.auto_dm_text;
        if (bcid && typeof d.bot_chat_id === 'string') bcid.value = d.bot_chat_id;
        // Токен не показываем — только флаг «сохранён» + маску в placeholder
        if (bt && d.bot_token_set) {
            bt.placeholder = d.bot_token_masked || '•••• сохранён';
            if (btSaved) btSaved.style.display = '';
        } else if (bt) {
            bt.placeholder = '123456789:AA...';
            if (btSaved) btSaved.style.display = 'none';
        }
        // Fallback-блок раскрыт по умолчанию только если бот НЕ настроен —
        // чтобы клиент сразу видел куда пойдут уведомления если бот пустой.
        // Если бот есть — fallback свёрнут (он не используется).
        var fb = document.getElementById('monitor-fallback-wrap');
        if (fb) fb.open = !d.bot_token_set;
        _updateMonitorUI(d);
        updateContactedCount();
    }).catch(function() {});
}

function saveMonitorAutostart() {
    var el = document.getElementById('monitor-autostart');
    if (!el) return;
    var enabled = !!el.checked;
    API.post('/api/monitor/autostart', {enabled: enabled}).then(function(d) {
        if (d && d.ok) {
            showToast(enabled ? 'Автостарт мониторинга включён' : 'Автостарт мониторинга выключен', 'info');
        }
    }).catch(function() {
        showToast('Не удалось сохранить настройку', 'error');
    });
}

function _updateMonitorUI(d) {
    var running = !!(d && d.running);
    var status = document.getElementById('monitor-status');
    var btnStart = document.getElementById('btn-monitor-start');
    var btnStop = document.getElementById('btn-monitor-stop');
    if (status) status.style.display = running ? 'block' : 'none';
    if (btnStart) btnStart.style.display = running ? 'none' : '';
    if (btnStop) btnStop.style.display = running ? '' : 'none';
    if (running && d.stats) {
        var m = document.getElementById('monitor-matched');
        if (m) m.textContent = 'Алертов: ' + (d.stats.matched || 0);
        var info = document.getElementById('monitor-info');
        if (info) {
            var parts = ['Чатов подписано: ' + (d.stats.resolved || 0)];
            if (d.stats.auto_dm_sent) {
                parts.push('Авто-DM отправлено: ' + d.stats.auto_dm_sent);
            }
            if (d.stats.auto_dm_skipped_dup) {
                parts.push('Дубли пропущено: ' + d.stats.auto_dm_skipped_dup);
            }
            if (d.stats.failed && d.stats.failed.length) {
                parts.push('Не открылись: ' + d.stats.failed.length);
            }
            info.textContent = parts.join('  •  ');
        }
    }
    // Polling работает пока открыта вкладка Мониторинг — даже когда running=false,
    // чтобы подхватить старт из scheduler-autoload (клиент открыл вкладку ДО того
    // как scheduler стартовал монитор, тогда initial load видел running=false,
    // таймер не запускался, UI не обновлялся, клиент видел «Старт» у работающего
    // мониторинга). Таймер гасится только если вкладка не Monitor.
    if (_currentTab === 'monitor' && !_monitorPollTimer) {
        _monitorPollTimer = setInterval(function() {
            if (_currentTab !== 'monitor') {
                clearInterval(_monitorPollTimer); _monitorPollTimer = null;
                return;
            }
            API.get('/api/monitor/config').then(function(dd) {
                _updateMonitorUI(dd);
            }).catch(function() {});
        }, 3000);
    } else if (_currentTab !== 'monitor' && _monitorPollTimer) {
        clearInterval(_monitorPollTimer); _monitorPollTimer = null;
    }
}

function startMonitor() {
    var chats = (document.getElementById('monitor-chats') || {}).value || '';
    var keywords = (document.getElementById('monitor-keywords') || {}).value || '';
    var ignoreWords = (document.getElementById('monitor-ignore-words') || {}).value || '';
    var extraPhonesRaw = (document.getElementById('monitor-extra-phones') || {}).value || '';
    var multiMode = (document.getElementById('monitor-multi-mode') || {}).value || 'redundant';
    var target = (document.getElementById('monitor-target') || {}).value || 'me';
    var autoDm = (document.getElementById('monitor-auto-dm') || {}).value || '';
    var botToken = (document.getElementById('monitor-bot-token') || {}).value || '';
    var botChatId = (document.getElementById('monitor-bot-chat-id') || {}).value || '';
    if (!chats.trim()) { showToast('Добавьте чаты', 'warning'); return; }
    if (!keywords.trim()) { showToast('Добавьте ключевые слова', 'warning'); return; }
    // Приоритет: акк из папки «Мониторинг» → потом глобальный активный.
    // Раньше всегда брался _cachedActive, и софт стартовал не тот акк который
    // лежит в папке Мониторинг/.
    var phone = '';
    var monAcc = (_cachedAccounts || []).find(function(a) {
        return (a.folder || '') === 'Мониторинг' && !a.dead && !a.frozen;
    });
    if (monAcc) phone = monAcc.phone;
    if (!phone) phone = _cachedActive || '';
    // Собираем список телефонов: основной + допы из textarea
    var phones = phone ? [phone] : [];
    extraPhonesRaw.split(/[\n\r,;]+/).forEach(function(p) {
        p = (p || '').trim().replace(/^\+/, '').replace(/\D/g, '');
        if (p && phones.indexOf(p) === -1) phones.push(p);
    });
    var payload = {
        chats: chats, keywords: keywords,
        ignore_words: ignoreWords.trim(),
        target: target,
        phones: phones,
        phone: phone,  // для совместимости со старым бэком
        multi_mode: multiMode,
        auto_dm_text: autoDm.trim(),
        bot_chat_id: botChatId.trim(),
    };
    if (botToken.trim()) payload.bot_token = botToken.trim();
    else payload.bot_token_keep = true;
    payload.ad_config = readAdConfig('monitor');
    API.post('/api/monitor/start', payload).then(function(d) {
        if (d.ok) {
            var msg = 'Мониторинг запущен: ' + d.chats + ' чатов, ' + d.keywords + ' ключей';
            if (autoDm.trim()) msg += ' + авто-DM автору коммента';
            showToast(msg, 'success');
            loadMonitor();
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    }).catch(function(e) {
        var msg = (e && e.message) || 'Ошибка';
        showToast(msg, 'error');
        // Если «уже запущен» — это значит UI был stale, подтягиваем свежий статус
        // чтобы показалась кнопка Стоп (клиент жаловался: вижу Старт у работающего
        // мониторинга, жму — говорит «уже запущен»).
        if (msg.indexOf('уже запущен') !== -1) {
            loadMonitor();
        }
    });
}

function resetContactedList() {
    if (!confirm('Сбросить список «кому уже писали»? После этого авто-DM начнёт писать тем же юзерам заново.')) return;
    API.post('/api/monitor/contacted_reset', {}).then(function(d) {
        if (d.ok) {
            showToast('Список сброшен. Следующий матч снова напишет.', 'success');
            updateContactedCount();
        }
    }).catch(function(e) { showToast((e && e.message) || 'Ошибка', 'error'); });
}

function detectChatId() {
    var tokEl = document.getElementById('monitor-bot-token');
    var cidEl = document.getElementById('monitor-bot-chat-id');
    var tokVal = tokEl ? tokEl.value.trim() : '';
    showToast('Запрашиваю getUpdates у бота...', 'info');
    API.post('/api/monitor/detect_chat_id', {token: tokVal}).then(function(d) {
        if (d.ok) {
            if (cidEl) cidEl.value = d.chat_id;
            var msg = 'Chat ID найден: ' + d.chat_id;
            if (d.user_info) msg += ' (' + d.user_info + ')';
            showToast(msg, 'success');
        } else {
            showToast(d.error || 'Не удалось определить', 'warning');
        }
    }).catch(function(e) {
        showToast((e && e.message) || 'Ошибка', 'error');
    });
}

function testMonitorBot() {
    var tokEl = document.getElementById('monitor-bot-token');
    var cidEl = document.getElementById('monitor-bot-chat-id');
    // Если поле токена пустое — сервер возьмёт сохранённый
    var payload = {
        token: (tokEl ? tokEl.value.trim() : ''),
        chat_id: (cidEl ? cidEl.value.trim() : ''),
    };
    showToast('Отправляю тестовое сообщение...', 'info');
    API.post('/api/monitor/test_bot', payload).then(function(d) {
        if (d.ok) {
            showToast(d.message || 'Тест ушёл', 'success');
        } else {
            showToast(d.error || 'Не получилось', 'error');
        }
    }).catch(function(e) {
        showToast((e && e.message) || 'Ошибка', 'error');
    });
}

function addToIgnoreList() {
    var inp = document.getElementById('monitor-ignore-input');
    if (!inp) return;
    var val = (inp.value || '').trim();
    if (!val) { showToast('Введи @username или user_id', 'warning'); return; }
    API.post('/api/monitor/contacted_add', {user: val}).then(function(d) {
        if (d.ok) {
            showToast('В игнор добавлен id=' + d.user_id + ' (всего ' + d.total + ')', 'success');
            inp.value = '';
            updateContactedCount();
        } else {
            showToast(d.error || 'Не удалось', 'error');
        }
    }).catch(function(e) {
        showToast((e && e.message) || 'Ошибка', 'error');
    });
}

function openIgnoreDb() {
    var modal = document.createElement('div');
    modal.id = 'ignore-db-modal';
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:600;display:flex;align-items:center;justify-content:center;';
    modal.onclick = function(e) { if (e.target === modal) modal.remove(); };
    modal.innerHTML =
        '<div style="background:var(--bg-card-hover);border:1px solid var(--border);border-radius:14px;padding:18px;max-width:640px;width:92%;max-height:88vh;display:flex;flex-direction:column;">' +
            '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;">' +
                '<h3 style="color:#fff;font-size:1.05rem;margin:0;"><i class="bi bi-database"></i> База игнор-листа мониторинга</h3>' +
                '<button class="btn btn-outline btn-sm" style="padding:2px 10px;font-size:0.7rem;" onclick="document.getElementById(\'ignore-db-modal\').remove()">✕</button>' +
            '</div>' +
            '<div style="display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap;">' +
                '<input type="text" id="ignore-db-search" placeholder="Поиск по id..." oninput="_loadIgnoreDb(0)" style="flex:1;min-width:140px;padding:5px 10px;font-size:0.78rem;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);">' +
                '<button class="btn btn-outline btn-sm" style="padding:4px 10px;font-size:0.7rem;" onclick="_importIgnoreDb()" title="Импорт из текста"><i class="bi bi-cloud-upload"></i> Импорт</button>' +
                '<button class="btn btn-outline btn-sm" style="padding:4px 10px;font-size:0.7rem;" onclick="_exportIgnoreDb()" title="Сохранить .txt"><i class="bi bi-download"></i> Экспорт</button>' +
            '</div>' +
            '<div id="ignore-db-stats" style="font-size:0.72rem;color:var(--text-muted);margin-bottom:8px;">Загрузка…</div>' +
            '<div id="ignore-db-list" style="flex:1;overflow-y:auto;background:var(--bg-input);border:1px solid var(--border);border-radius:8px;padding:6px;font-family:monospace;font-size:0.78rem;min-height:200px;"></div>' +
            '<div style="margin-top:10px;display:flex;gap:6px;justify-content:flex-end;flex-wrap:wrap;">' +
                '<button class="btn btn-outline btn-sm" style="padding:4px 12px;font-size:0.72rem;" onclick="_loadIgnoreDb(0)"><i class="bi bi-arrow-clockwise"></i> Обновить</button>' +
                '<button class="btn btn-danger btn-sm" style="padding:4px 12px;font-size:0.72rem;" onclick="resetContactedList(); document.getElementById(\'ignore-db-modal\').remove();"><i class="bi bi-trash"></i> Очистить всё</button>' +
            '</div>' +
        '</div>';
    document.body.appendChild(modal);
    _loadIgnoreDb(0);
}
var _ignoreDbOffset = 0;
var _ignoreDbLimit = 500;
function _loadIgnoreDb(offset) {
    _ignoreDbOffset = offset || 0;
    var q = (document.getElementById('ignore-db-search') || {}).value || '';
    var stats = document.getElementById('ignore-db-stats');
    var list = document.getElementById('ignore-db-list');
    if (!list) return;
    list.innerHTML = '<div style="text-align:center;color:var(--text-muted);padding:20px;"><i class="bi bi-arrow-repeat" style="animation:spin 1s linear infinite;"></i></div>';
    API.get('/api/monitor/contacted_list?offset=' + _ignoreDbOffset + '&limit=' + _ignoreDbLimit + (q ? '&q=' + encodeURIComponent(q) : '')).then(function(d) {
        if (!d.ok) {
            list.innerHTML = '<div style="color:#f87171;padding:20px;">' + escHtml(d.error || 'Ошибка') + '</div>';
            return;
        }
        if (stats) stats.textContent = 'Всего: ' + d.total + (q ? ' (фильтр)' : '') + ' • Показано: ' + (d.ids || []).length + ' (с ' + d.offset + ')';
        if (!d.ids || !d.ids.length) {
            list.innerHTML = '<div style="text-align:center;color:var(--text-muted);padding:20px;">База пуста</div>';
            return;
        }
        list.innerHTML = d.ids.map(function(id) {
            return '<div style="display:flex;align-items:center;gap:10px;padding:4px 8px;border-bottom:1px solid #1a1a2a;">' +
                '<span style="flex:1;color:var(--text-primary);">' + id + '</span>' +
                '<button onclick="_removeFromIgnore(' + id + ')" class="btn btn-outline btn-sm" style="padding:1px 8px;font-size:0.65rem;color:var(--danger);" title="Убрать из игнора"><i class="bi bi-x"></i></button>' +
            '</div>';
        }).join('');
        // пагинация
        if (d.total > _ignoreDbOffset + d.ids.length) {
            list.innerHTML += '<div style="text-align:center;padding:8px;"><button class="btn btn-outline btn-sm" style="padding:3px 14px;font-size:0.7rem;" onclick="_loadIgnoreDb(' + (_ignoreDbOffset + _ignoreDbLimit) + ')">Показать ещё</button></div>';
        }
    }).catch(function() {
        list.innerHTML = '<div style="color:#f87171;padding:20px;">Ошибка сети</div>';
    });
}
function _removeFromIgnore(id) {
    API.post('/api/monitor/contacted_remove', {ids: [id]}).then(function(d) {
        if (d.ok) {
            showToast('Удалён id=' + id + ' (осталось ' + d.total + ')', 'success');
            _loadIgnoreDb(_ignoreDbOffset);
            updateContactedCount();
        } else showToast(d.error || 'Ошибка', 'error');
    }).catch(function() { showToast('Ошибка сети', 'error'); });
}
function _importIgnoreDb() {
    var modal = document.createElement('div');
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.75);z-index:700;display:flex;align-items:center;justify-content:center;';
    modal.onclick = function(e) { if (e.target === modal) modal.remove(); };
    modal.innerHTML =
        '<div style="background:var(--bg-card-hover);border:1px solid var(--border);border-radius:12px;padding:18px;max-width:480px;width:90%;">' +
            '<h3 style="color:#fff;font-size:1rem;margin-bottom:10px;"><i class="bi bi-cloud-upload"></i> Импорт user_id в игнор-базу</h3>' +
            '<textarea id="_ig-import-text" placeholder="123456789\n987654321\n555111222" rows="8" style="width:100%;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);padding:8px 12px;font-family:monospace;font-size:0.78rem;outline:none;"></textarea>' +
            '<div style="font-size:0.7rem;color:var(--text-muted);margin-top:6px;">По одному id на строке (или через запятую). @username не поддерживается — нужны цифровые id.</div>' +
            '<div style="display:flex;gap:6px;margin-top:10px;align-items:center;">' +
                '<label style="display:flex;align-items:center;gap:6px;font-size:0.75rem;cursor:pointer;"><input type="radio" name="_ig-mode" value="merge" checked> Дополнить</label>' +
                '<label style="display:flex;align-items:center;gap:6px;font-size:0.75rem;cursor:pointer;"><input type="radio" name="_ig-mode" value="replace"> Заменить полностью</label>' +
                '<button class="btn btn-accent btn-sm" style="margin-left:auto;padding:5px 14px;font-size:0.75rem;" onclick="_doImportIgnore(this)">Загрузить</button>' +
                '<button class="btn btn-outline btn-sm" style="padding:5px 12px;font-size:0.75rem;" onclick="this.closest(\'div[style*=fixed]\').remove();">Отмена</button>' +
            '</div>' +
        '</div>';
    document.body.appendChild(modal);
}
function _doImportIgnore(btn) {
    var text = (document.getElementById('_ig-import-text') || {}).value || '';
    var mode = 'merge';
    document.querySelectorAll('input[name="_ig-mode"]').forEach(function(r) { if (r.checked) mode = r.value; });
    if (!text.trim()) { showToast('Текст пустой', 'warning'); return; }
    API.post('/api/monitor/contacted_import', {text: text, mode: mode}).then(function(d) {
        if (d.ok) {
            showToast('Импортировано: +' + d.added + ' (распознано ' + d.parsed + ', всего ' + d.total + ')', 'success');
            btn.closest('div[style*="fixed"]').remove();
            _loadIgnoreDb(0);
            updateContactedCount();
        } else showToast(d.error || 'Ошибка', 'error');
    }).catch(function() { showToast('Ошибка сети', 'error'); });
}
function _exportIgnoreDb() {
    API.get('/api/monitor/contacted_export').then(function(d) {
        if (d.ok) showToast('Сохранено в ' + d.path + ' (' + d.count + ' id)', 'success');
        else showToast(d.error || 'Ошибка', 'error');
    }).catch(function() { showToast('Ошибка сети', 'error'); });
}

function updateContactedCount() {
    var el = document.getElementById('monitor-contacted-count');
    if (!el) return;
    API.get('/api/monitor/contacted_count').then(function(d) {
        if (d && typeof d.count === 'number') {
            el.textContent = 'В базе: ' + d.count + ' юзеров';
            el.style.color = d.count > 0 ? 'var(--accent-light)' : 'var(--text-muted)';
        }
    }).catch(function() {});
}

// Общий хелпер — выгрузка результатов задачи в папку с категориями
function _doExport(url, label) {
    showToast('Раскладываю результаты ' + label + '...', 'info');
    API.get(url).then(function(d) {
        if (d && d.ok) {
            var parts = [];
            var sum = d.summary || {};
            for (var k in sum) parts.push(k + ': ' + sum[k]);
            showToast(parts.join(' • ') || ('Всего: ' + (d.ok_count + d.fail_count)), 'success');
            if (typeof addLog === 'function') addLog(label + ': ' + (d.folder || ''), 'SUCCESS');
        } else {
            showToast((d && d.error) || 'Ошибка', 'error');
        }
    }).catch(function(e) { showToast((e && e.error) || 'Ошибка', 'error'); });
}

function exportJoinResults() { _doExport('/api/join/export_results', 'подписки'); }
function exportSpamResults() { _doExport('/api/spam/export_results', 'рассылки'); }
function exportDmResults()   { _doExport('/api/dm/export_results', 'DM'); }

// Повторить failed — подтянуть список неудачных целей в input и нажать Старт
function _retryFailed(kind, inputId, startFn) {
    API.post('/api/retry_failed', {kind: kind}).then(function(d) {
        if (!d.ok) { showToast(d.error || 'Ошибка', 'error'); return; }
        if (!d.count) { showToast('Нет неудачных целей — нечего повторять', 'info'); return; }
        var el = document.getElementById(inputId);
        if (!el) return;
        el.value = d.items.join('\n');
        el.dispatchEvent(new Event('input'));
        showToast('Загружено ' + d.count + ' целей для повтора. Выберите аккаунт и нажмите Старт.', 'success');
    }).catch(function() { showToast('Ошибка', 'error'); });
}
function retryFailedJoin() { _retryFailed('join', 'join-links'); }
function retryFailedSpam() { _retryFailed('spam', 'spam-targets-input'); }
function retryFailedDm()   { _retryFailed('dm', 'dm-targets-input'); }

// Копирование в буфер — для текстовых превью (парсер/подписка)
function copyToClipboard(text, label) {
    if (!text) { showToast('Нечего копировать', 'warning'); return; }
    try {
        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(text).then(function() {
                showToast((label || 'Скопировано') + ' (' + text.split('\n').length + ' строк)', 'success');
            }).catch(function() {
                _fallbackCopy(text, label);
            });
        } else {
            _fallbackCopy(text, label);
        }
    } catch (e) { _fallbackCopy(text, label); }
}
function _fallbackCopy(text, label) {
    try {
        var ta = document.createElement('textarea');
        ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0';
        document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove();
        showToast((label || 'Скопировано') + ' (' + text.split('\n').length + ' строк)', 'success');
    } catch (e) { showToast('Копирование не сработало', 'error'); }
}

function openMonitorLog() {
    API.post('/api/monitor/open_log', {}).then(function(d) {
        if (!d.ok) showToast(d.error || 'Лог не найден', 'warning');
    }).catch(function() { showToast('Ошибка', 'error'); });
}

function stopMonitor() {
    API.post('/api/monitor/stop', {}).then(function(d) {
        if (d.ok) {
            showToast('Мониторинг остановлен. Алертов: ' + (d.matched || 0), 'info');
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
        loadMonitor();
    }).catch(function(e) {
        // Раньше ловили молча — клиент жал «Стоп», ничего не происходило,
        // казалось что кнопка не реагирует. Теперь явно показываем ошибку
        // и в любом случае подтягиваем актуальный статус мониторинга.
        showToast((e && e.message) || 'Не удалось остановить мониторинг', 'error');
        loadMonitor();
    });
}

// ── Toast Notifications ─────────────────────────────────────────

function showConfirmDialog(title, text, onConfirm, onCancel) {
    var old = document.getElementById('confirm-dialog-overlay');
    if (old) old.remove();

    var overlay = document.createElement('div');
    overlay.id = 'confirm-dialog-overlay';
    overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.55);z-index:9999;display:flex;align-items:center;justify-content:center;animation:fadeIn 0.15s ease;';

    var popup = document.createElement('div');
    popup.style.cssText = 'min-width:340px;max-width:420px;text-align:center;padding:28px 24px;background:var(--bg-card);border:1px solid var(--border);border-radius:16px;box-shadow:var(--shadow-lg);';
    popup.innerHTML =
        '<div style="margin-bottom:16px;">' +
            '<i class="bi bi-exclamation-triangle" style="font-size:2.2rem;color:var(--warning);"></i>' +
        '</div>' +
        '<div style="font-size:1rem;font-weight:700;color:var(--text-primary);margin-bottom:8px;">' + escHtml(title || 'Подтверждение') + '</div>' +
        '<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:24px;line-height:1.5;">' + escHtml(text || '') + '</div>' +
        '<div style="display:flex;gap:10px;justify-content:center;">' +
            '<button class="btn btn-accent" id="confirm-dialog-ok" style="padding:8px 28px;font-size:0.85rem;">Да, продолжить</button>' +
            '<button class="btn btn-outline" id="confirm-dialog-cancel" style="padding:8px 28px;font-size:0.85rem;">Отмена</button>' +
        '</div>';

    overlay.appendChild(popup);
    document.body.appendChild(overlay);

    document.getElementById('confirm-dialog-ok').onclick = function() {
        overlay.remove();
        if (onConfirm) onConfirm();
    };
    document.getElementById('confirm-dialog-cancel').onclick = function() {
        overlay.remove();
        if (onCancel) onCancel();
    };
    overlay.onclick = function(e) {
        if (e.target === overlay) {
            overlay.remove();
            if (onCancel) onCancel();
        }
    };
}

function showToast(msg, type) {
    type = type || 'info';
    var container = document.getElementById('toast-container');
    if (!container) return;
    var icons = {success: 'bi-check-circle', error: 'bi-x-circle', info: 'bi-info-circle', warning: 'bi-exclamation-triangle'};
    var toast = document.createElement('div');
    toast.className = 'toast ' + type;
    // Иконка безопасна (whitelist), msg приходит от бэкенда — экранируем на случай HTML-символов
    var iconClass = icons[type] || 'bi-info-circle';
    toast.innerHTML = '<i class="bi ' + iconClass + '"></i> ';
    var span = document.createElement('span');
    span.textContent = (typeof msg === 'string') ? msg : String(msg);
    toast.appendChild(span);
    container.appendChild(toast);
    setTimeout(function() {
        toast.style.opacity = '0';
        toast.style.transform = 'translateX(20px)';
        setTimeout(function() { toast.remove(); }, 300);
    }, 3000);
    // Log to console
    if (typeof addLog === 'function') {
        var lvl = type === 'error' ? 'ERROR' : type === 'success' ? 'SUCCESS' : type === 'warning' ? 'WARNING' : 'INFO';
        addLog(msg, lvl);
    }
}

// ── HTML Escape ─────────────────────────────────────────────────

function escHtml(s) {
    var d = document.createElement('div');
    d.textContent = s || '';
    return d.innerHTML;
}

// ── Sound Notifications ────────────────────────────────────────

function playSound(type) {
    try {
        var ctx = new (window.AudioContext || window.webkitAudioContext)();
        var osc = ctx.createOscillator();
        var gain = ctx.createGain();
        osc.connect(gain);
        gain.connect(ctx.destination);
        gain.gain.value = 0.15;

        if (type === 'success') {
            // Два тона вверх — задача завершена
            osc.frequency.value = 523; // C5
            osc.type = 'sine';
            osc.start(ctx.currentTime);
            osc.frequency.setValueAtTime(659, ctx.currentTime + 0.15); // E5
            osc.frequency.setValueAtTime(784, ctx.currentTime + 0.3); // G5
            gain.gain.setValueAtTime(0.15, ctx.currentTime + 0.4);
            gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.6);
            osc.stop(ctx.currentTime + 0.6);
        } else if (type === 'error') {
            // Два тона вниз — ошибка
            osc.frequency.value = 440; // A4
            osc.type = 'square';
            osc.start(ctx.currentTime);
            osc.frequency.setValueAtTime(330, ctx.currentTime + 0.2); // E4
            gain.gain.setValueAtTime(0.1, ctx.currentTime + 0.3);
            gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.5);
            osc.stop(ctx.currentTime + 0.5);
        } else {
            // Одиночный тон — уведомление
            osc.frequency.value = 600;
            osc.type = 'sine';
            osc.start(ctx.currentTime);
            gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.3);
            osc.stop(ctx.currentTime + 0.3);
        }
    } catch(e) {}
}

// ── Status Refresh ──────────────────────────────────────────────

var _refreshTimer = null;
var _hasActiveJobs = false;
var _prevJobStatuses = {};

function scheduleRefresh() {
    clearTimeout(_refreshTimer);
    var interval = _hasActiveJobs ? 1000 : 2000;
    _refreshTimer = setTimeout(function() {
        refreshStatus();
        scheduleRefresh();
    }, interval);
}

var _lastAccountsCount = -1;
function refreshStatus() {
    API.get('/api/status').then(function(d) {
        if (!d.ok) return;

        // Module access control
        if (d.allowed_modules) _allowedModules = d.allowed_modules;
        if (d.allowed_features) _allowedFeatures = d.allowed_features;
        if (d.key_type) _keyType = d.key_type;
        if (d.onboarding_done) window._serverOnboardingDone = true;
        _applyModuleLocks();

        // License expired handling
        var expBanner = document.getElementById('expired-banner');
        if (d.license_expired) {
            if (expBanner) expBanner.style.display = 'block';
            _applyExpiredLock(true);
        } else {
            if (expBanner) expBanner.style.display = 'none';
            _applyExpiredLock(false);
        }

        _hasActiveJobs = !!(d.join_job && d.join_job.status === 'running')
            || !!(d.spam_job && d.spam_job.status === 'running')
            || !!(d.user_spam_job && d.user_spam_job.status === 'running')
            || !!(d.warmup_job && d.warmup_job.status === 'running')
            || (d.multi_jobs || []).some(function(j) { return j.status === 'running'; });

        renderAllJobs(d);

        // Refresh warmup tab if active and job running
        if (_currentTab === 'warmup' && d.warmup_job && d.warmup_job.status === 'running') {
            loadWarmup();
        }

        if (d.accounts_count !== undefined) {
            checkOnboarding(d.accounts_count);
            var el = document.getElementById('accounts-count');
            if (el) el.textContent = d.accounts_count;
            if (_lastAccountsCount !== -1 && d.accounts_count !== _lastAccountsCount) {
                if (_currentTab === 'accounts') loadAccounts();
                if (_currentTab === 'multi') loadMultiAccounts();
                renderAccountSelectors();
            }
            _lastAccountsCount = d.accounts_count;
        }

        // Показываем баннер обновления (без автоустановки)
        if (d.update_available && !window._updateBannerShown) {
            window._updateBannerShown = true;
            var banner = document.getElementById('update-banner');
            var ver = document.getElementById('update-version');
            var cl = document.getElementById('update-changelog');
            if (banner) banner.style.display = 'flex';
            if (ver) ver.textContent = 'v' + d.update_version;
            if (cl && d.update_changelog) cl.textContent = d.update_changelog;
        }

        // Критичный оверлей: exe запущен прямо из архива WinRAR/7z без распаковки.
        // Каждый закрытый архив = потеря настроек/аккаунтов/обновлений.
        // Показываем полноэкранный блокирующий оверлей один раз, клиент его увидит
        // ДАЖЕ если не смотрит наверх окна.
        if (d.temp_launch_warning) {
            var tb = document.getElementById('temp-launch-overlay') || document.getElementById('temp-launch-banner');
            if (tb && !window._tempLaunchShown) {
                tb.style.display = (tb.id === 'temp-launch-overlay') ? 'flex' : 'block';
                window._tempLaunchShown = true;
            }
            var tp = document.getElementById('temp-launch-path');
            if (tp) tp.textContent = d.temp_launch_path || '(не определена)';
        }
    }).catch(function() {});
}

// ── Job Rendering ───────────────────────────────────────────────

function renderAllJobs(data) {
    var container = document.getElementById('active-jobs');
    if (!container) return;
    var jobs = [];
    if (data.join_job) jobs.push(Object.assign({}, data.join_job, {type: 'join'}));
    if (data.spam_job) jobs.push(Object.assign({}, data.spam_job, {type: 'spam'}));
    if (data.user_spam_job) jobs.push(Object.assign({}, data.user_spam_job, {type: 'user_spam'}));
    if (data.warmup_job && data.warmup_job.status !== 'stopped' && data.warmup_job.status !== 'finished') {
        jobs.push(Object.assign({}, data.warmup_job, {type: 'warmup', phone: 'warmup'}));
    }
    (data.multi_jobs || []).forEach(function(j) {
        jobs.push(Object.assign({}, j, {is_multi: true}));
    });
    if (data.parser_job && data.parser_job.status === 'running') {
        var pj = data.parser_job;
        jobs.push({
            type: 'parser',
            phone: 'парсинг',
            status: 'running',
            processed: pj.found || 0,
            total: Math.max((pj.found || 0) + 1, (pj.scanned || 0) + 1),
            current_step: pj.status_text || ('Найдено: ' + (pj.found || 0) + ' | Проверено: ' + (pj.scanned || 0) + ' | ' + pj.elapsed + 'с'),
            is_parser: true,
        });
    }

    // Detect job completion — play sound + лог в консоль.
    // Раньше требовалось чтобы prev был 'running' — если между пулами задача
    // успевала проскочить сразу в 'finished', клиент не видел уведомления.
    // Теперь срабатывает один раз на завершение даже если running-снапшот
    // был потерян; _finishedKeys не даёт показать дважды.
    jobs.forEach(function(j) {
        var key = (j.type || 'job') + '_' + (j.phone || '');
        var prev = _prevJobStatuses[key];
        var finishedKey = '__f__' + key;
        var label = j.type === 'join' ? 'Подписка' : j.type === 'user_spam' ? 'DM' : 'Рассылка';
        if (j.status === 'finished' && !_prevJobStatuses[finishedKey]) {
            _prevJobStatuses[finishedKey] = true;
            playSound('success');
            showToast(label + ' завершена! +' + (j.phone || ''), 'success');
            if (typeof addLog === 'function') {
                addLog(label + ' завершена! +' + (j.phone || ''), 'SUCCESS');
            }
        } else if (j.status === 'stopped' && prev === 'running') {
            playSound('error');
        }
        _prevJobStatuses[key] = j.status;
    });

    if (jobs.length === 0) {
        container.innerHTML = '<div style="text-align:center;padding:32px;color:var(--text-muted);"><i class="bi bi-moon-stars" style="font-size:2rem;display:block;margin-bottom:8px;"></i>Нет активных задач</div>';
        return;
    }
    container.innerHTML = jobs.map(function(j) { return renderJobCard(j); }).join('');
}

function renderJobCard(job) {
    var isJoin = job.type === 'join';
    var isDm = job.type === 'user_spam';
    var isWarmup = job.type === 'warmup';
    var isParser = job.type === 'parser';
    var icon = isParser ? 'bi-search' : (isWarmup ? 'bi-fire' : (isJoin ? 'bi-box-arrow-in-right' : (isDm ? 'bi-person-lines-fill' : 'bi-megaphone')));
    var color = isParser ? '#a855f7' : (isWarmup ? '#f97316' : (isJoin ? 'var(--success)' : (isDm ? 'var(--pink)' : 'var(--info)')));
    var label = isParser ? 'Парсинг' : (isWarmup ? ('Прогрев ' + (job.stage_icon || '') + ' ' + (job.stage_label || '')) : (isJoin ? 'Подписка' : (isDm ? 'DM рассылка' : 'Рассылка')));
    var pct = job.total > 0 ? Math.round(job.processed / job.total * 100) : 0;
    var statusMap = {running: 'Работает', paused: 'Пауза', finished: 'Завершено', stopped: 'Стоп'};
    var badgeMap = {running: 'badge-running', paused: 'badge-paused', finished: 'badge-finished', stopped: 'badge-stopped'};

    var controls = '';
    if (job.status === 'running' || job.status === 'paused') {
        controls = '<div class="d-flex gap-2" style="margin-top:8px;">';
        // Парсер не поддерживает pause — только стоп
        if (!isParser) {
            if (job.status === 'running') {
                controls += '<button class="btn btn-outline btn-sm" onclick="jobAction(\'pause\',\'' + job.type + '\')"><i class="bi bi-pause-fill"></i> Пауза</button>';
            } else {
                controls += '<button class="btn btn-success btn-sm" onclick="jobAction(\'resume\',\'' + job.type + '\')"><i class="bi bi-play-fill"></i> Продолжить</button>';
            }
        }
        var stopArgs;
        if (isParser) stopArgs = "stopParser()";
        else if (job.is_multi) stopArgs = "multiJobStop('" + (job.phone || '') + "','" + job.type + "')";
        else stopArgs = "jobAction('stop','" + job.type + "')";
        controls += '<button class="btn btn-danger btn-sm" onclick="' + stopArgs + '"><i class="bi bi-stop-fill"></i> Стоп</button>';
        controls += '</div>';
    }

    var breakdown = '';
    var c = job.counts || {};
    var isDmLike = isDm || job.type === 'dm';
    var hasBreakdown = (isJoin || isDmLike || job.type === 'spam') && (
        (c.sent || 0) + (c.failed || 0) + (c.no_rights || 0) +
        (c.spamfilter || 0) + (c.flood || 0) + (c.slowmode || 0) +
        (c.joined || 0) + (c.already_member || 0) > 0
    );
    if (hasBreakdown) {
        var parts = [];
        var sentKey = isJoin ? (c.joined || 0) : (c.sent || 0);
        if (sentKey) parts.push('<span style="color:var(--success);"><i class="bi bi-check-circle-fill"></i> ' + sentKey + '</span>');
        if (c.already_member) parts.push('<span style="color:var(--text-muted-strong);" title="уже состоит в чате">↩ уже ' + c.already_member + '</span>');
        if (c.no_rights) parts.push('<span style="color:var(--warning);" title="приватность/нет прав"><i class="bi bi-lock-fill"></i> приватность ' + c.no_rights + '</span>');
        if (c.spamfilter) parts.push('<span style="color:var(--warning);" title="спам-фильтр Telegram"><i class="bi bi-slash-circle-fill"></i> спам-блок ' + c.spamfilter + '</span>');
        if (c.flood) parts.push('<span style="color:var(--warning);" title="Временный флуд-блок от Telegram"><i class="bi bi-hourglass-split"></i> флуд ' + c.flood + '</span>');
        if (c.slowmode) parts.push('<span style="color:var(--text-muted-strong);" title="медленный режим"><i class="bi bi-speedometer"></i> медл-режим ' + c.slowmode + '</span>');
        if (c.failed) parts.push('<span style="color:var(--danger);" title="username не найден, приватность и т.п."><i class="bi bi-x-circle-fill"></i> ' + c.failed + '</span>');
        breakdown = '<div class="text-sm" style="margin-top:6px;display:flex;gap:10px;flex-wrap:wrap;">' + parts.join('') + '</div>';
    }

    return '<div class="card mb-3" style="border-left:3px solid ' + color + ';">' +
        '<div class="card-body">' +
            '<div class="d-flex align-center justify-between mb-2">' +
                '<span><i class="bi ' + icon + '" style="color:' + color + ';margin-right:6px;"></i><strong>' + label + '</strong> <span class="text-muted text-sm">+' + escHtml(job.phone || '') + '</span></span>' +
                '<span class="badge ' + (badgeMap[job.status] || '') + '">' + (statusMap[job.status] || job.status) + '</span>' +
            '</div>' +
            '<div class="progress mb-2"><div class="progress-bar" style="width:' + pct + '%;"></div></div>' +
            '<div class="d-flex justify-between text-sm text-muted">' +
                '<span>' + job.processed + '/' + job.total + ' (' + pct + '%)</span>' +
                '<span>' + escHtml(job.current_step || '') + '</span>' +
            '</div>' +
            breakdown +
            controls +
        '</div>' +
    '</div>';
}

function jobAction(action, type) {
    API.post('/api/job/' + action, {type: type}).then(function(d) {
        if (d.ok) showToast(action === 'pause' ? 'Приостановлено' : action === 'resume' ? 'Возобновлено' : 'Остановлено', 'success');
        refreshStatus();
    }).catch(function() { showToast('Ошибка', 'error'); });
}

function multiJobStop(phone, type) {
    showConfirmDialog('Остановить задачу?', 'Аккаунт +' + phone + ' будет остановлен. Уже сделанное сохранится.', function() {
        API.post('/api/multi/stop', {phone: phone, type: type}).then(function(d) {
            if (d.ok) showToast('Остановлено: ' + d.stopped + ' задач', 'success');
            else showToast(d.error || 'Ошибка', 'error');
            refreshStatus();
        }).catch(function() { showToast('Ошибка', 'error'); });
    });
}

function multiStopAll() {
    showConfirmDialog('Остановить все мульти-задачи?', 'Все аккаунты будут остановлены одновременно.', function() {
        API.post('/api/multi/stop', {}).then(function(d) {
            if (d.ok) showToast('Остановлено: ' + d.stopped + ' задач', 'success');
            refreshStatus();
        }).catch(function() { showToast('Ошибка', 'error'); });
    });
}

// ══════════════════════════════════════════════════════════════════
//  SPAM — рассылка по чатам
// ══════════════════════════════════════════════════════════════════

var _spamTargets = [];

function collectTargets() {
    showToast('Собираю чаты аккаунта...', 'info');
    API.post('/api/spam/targets/collect').then(function(d) {
        if (d.ok) {
            _spamTargets = [];
            for (var i = 0; i < d.count; i++) _spamTargets.push(i);
            document.getElementById('spam-count').textContent = d.count;
            document.getElementById('btn-spam').disabled = d.count === 0;
            showToast('Найдено ' + d.count + ' чатов для рассылки', 'success');
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    }).catch(function(e) { showToast(e.error || 'Ошибка сбора чатов', 'error'); });
}

function toggleManualTargets(type) {
    var el = document.getElementById(type + '-manual');
    if (el) el.style.display = el.style.display === 'none' ? 'block' : 'none';
}

function addManualTargets(type) {
    var input = document.getElementById(type + '-targets-input');
    if (!input) return;
    var text = input.value.trim();
    if (!text) return;

    if (type === 'spam') {
        API.post('/api/spam/targets/manual', {text: text}).then(function(d) {
            if (d.ok) {
                document.getElementById('spam-count').textContent = d.count;
                document.getElementById('btn-spam').disabled = d.count === 0;
                input.value = '';
                showToast('Добавлено. Всего: ' + d.count + ' чатов', 'success');
            }
        }).catch(function() { showToast('Ошибка', 'error'); });
    }
}

function clearTargets(type) {
    if (type === 'spam') {
        API.post('/api/spam/targets/clear').then(function() {
            document.getElementById('spam-count').textContent = '0';
            document.getElementById('btn-spam').disabled = true;
            showToast('Список чатов очищен', 'info');
        });
    } else if (type === 'dm') {
        API.post('/api/dm/targets/clear').then(function() {
            document.getElementById('dm-count').textContent = '0';
            document.getElementById('btn-dm').disabled = true;
            showToast('Список юзеров очищен', 'info');
        });
    }
}


// ══════════════════════════════════════════════════════════════════
//  Anti-detect helper — собирает флаги «Защита аккаунта» с любого модуля
// ══════════════════════════════════════════════════════════════════

function readAdConfig(prefix) {
    function v(id) {
        var el = document.getElementById(prefix + '-' + id);
        return el && el.checked;
    }
    return {
        pre_spam_check: !!v('pre-spam-check'),
        solve_captcha: !!v('solve-captcha'),
        online: !!v('keep-online'),
        read_messages: !!v('read-msgs'),
        mark_read: !!v('mark-read'),
        view_profiles: !!v('view-profile'),
        view_photos: !!v('view-photo'),
    };
}

function saveGlobalAdSettings() {
    var pre = document.getElementById('global-pre-spam-check');
    var payload = { pre_spam_check: !!(pre && pre.checked) };
    API.post('/api/global_ad/save', payload).then(function(d) {
        if (d && d.ok) showToast('Глобальная защита сохранена', 'success');
    }).catch(function() {});
}

function onSpamModeChange() {
    var mode = (document.querySelector('input[name="spam-mode"]:checked') || {}).value || 'text';
    document.getElementById('spam-mode-text-block').style.display = mode === 'text' ? '' : 'none';
    document.getElementById('spam-mode-forward-block').style.display = mode === 'forward' ? '' : 'none';
}

function startSpam() {
    var ad = readAdConfig('spam');
    var mode = (document.querySelector('input[name="spam-mode"]:checked') || {}).value || 'text';

    var payload = { ad_config: ad };
    var loopEl = document.getElementById('spam-loop');
    if (loopEl && loopEl.checked) {
        payload.loop_hours = parseInt(document.getElementById('spam-loop-hours').value || '6', 10);
    }
    var capEl = document.getElementById('spam-max-per-acc');
    if (capEl) {
        var capVal = parseInt(capEl.value || '0', 10);
        if (!isNaN(capVal) && capVal > 0) payload.max_per_acc = capVal;
    }

    if (mode === 'forward') {
        var srcChat = document.getElementById('spam-forward-chat').value.trim();
        var msgId = parseInt(document.getElementById('spam-forward-msg-id').value || '0', 10);
        if (!srcChat || !msgId) { showToast('Укажи источник и ID сообщения', 'warning'); return; }
        payload.forward_from_chat = srcChat;
        payload.forward_msg_id = msgId;
        // В режиме пересылки текст не нужен — но ставим заглушку для backend-валидации
        return API.post('/api/spam/template', {template: '[forward]'}).then(function() {
            return API.post('/api/spam/start', payload);
        }).then(function(d) {
            if (d.ok) {
                showToast('Пересылка запущена! ' + d.total + ' чатов', 'success');
                document.getElementById('btn-spam').disabled = true;
                refreshStatus();
            } else {
                showToast(d.error || 'Ошибка запуска', 'error');
            }
        }).catch(function(e) { showToast(e.error || 'Ошибка', 'error'); });
    }

    var tpl = document.getElementById('spam-tpl').value.trim();
    if (!tpl) { showToast('Введите текст сообщения', 'warning'); return; }
    var parseMode = document.getElementById('spam-parse-mode').value || '';
    if (parseMode) payload.parse_mode = parseMode;
    // Save template first
    API.post('/api/spam/template', {template: tpl}).then(function() {
        return API.post('/api/spam/start', payload);
    }).then(function(d) {
        if (d.ok) {
            showToast('Рассылка запущена! ' + d.total + ' чатов', 'success');
            document.getElementById('btn-spam').disabled = true;
            refreshStatus();
        } else {
            showToast(d.error || 'Ошибка запуска', 'error');
        }
    }).catch(function(e) { showToast(e.error || 'Ошибка запуска рассылки', 'error'); });
}

// ══════════════════════════════════════════════════════════════════
//  DM — рассылка в личные сообщения
// ══════════════════════════════════════════════════════════════════

function loadDmTargets() {
    var input = document.getElementById('dm-targets-input');
    if (!input) return;
    var text = input.value.trim();
    if (!text) { showToast('Введите юзернеймы или ID', 'warning'); return; }

    API.post('/api/dm/targets', {text: text}).then(function(d) {
        if (d.ok) {
            document.getElementById('dm-count').textContent = d.count;
            document.getElementById('btn-dm').disabled = d.count === 0;
            showToast('Загружено ' + d.count + ' юзеров', 'success');
            if (d.failed && d.failed.length > 0) {
                showToast('Не распознано: ' + d.failed.join(', '), 'warning');
            }
        }
    }).catch(function(e) { showToast(e.error || 'Ошибка загрузки', 'error'); });
}

function loadDmFile(input) {
    if (!input.files || !input.files[0]) return;
    var reader = new FileReader();
    reader.onload = function(e) {
        var text = e.target.result;
        document.getElementById('dm-targets-input').value = text;
        loadDmTargets();
    };
    reader.readAsText(input.files[0]);
}

function startDm() {
    var tpl = document.getElementById('dm-tpl').value.trim();
    if (!tpl) { showToast('Введите текст сообщения', 'warning'); return; }

    API.post('/api/dm/template', {template: tpl}).then(function(d) {
        // Check trigger warnings
        if (d.trigger_warnings && d.trigger_warnings.length > 0) {
            var warnEl = document.getElementById('dm-trigger-warn');
            var wordsEl = document.getElementById('dm-trigger-words');
            if (warnEl && wordsEl) {
                wordsEl.textContent = 'Осторожно! Слова-триггеры: ' + d.trigger_warnings.join(', ');
                warnEl.style.display = 'block';
            }
        }
        var dmPayload = {ad_config: readAdConfig('dm')};
        var pmEl = document.getElementById('dm-parse-mode');
        if (pmEl && pmEl.value) dmPayload.parse_mode = pmEl.value;
        var dmLoopEl = document.getElementById('dm-loop');
        if (dmLoopEl && dmLoopEl.checked) {
            dmPayload.loop_hours = parseInt(document.getElementById('dm-loop-hours').value || '6', 10);
        }
        var capEl = document.getElementById('dm-max-per-acc');
        if (capEl) {
            var capVal = parseInt(capEl.value || '0', 10);
            if (!isNaN(capVal) && capVal > 0) dmPayload.max_per_acc = capVal;
        }
        return API.post('/api/dm/start', dmPayload);
    }).then(function(d) {
        if (d.ok) {
            showToast('DM рассылка запущена! ' + d.total + ' юзеров', 'success');
            document.getElementById('btn-dm').disabled = true;
            refreshStatus();
        } else {
            showToast(d.error || 'Ошибка запуска', 'error');
        }
    }).catch(function(e) { showToast(e.error || 'Ошибка запуска DM', 'error'); });
}

// ══════════════════════════════════════════════════════════════════
//  JOIN — вступление в чаты
// ══════════════════════════════════════════════════════════════════

function loadJoinFile(input) {
    if (!input.files || !input.files[0]) return;
    var reader = new FileReader();
    reader.onload = function(e) {
        var el = document.getElementById('join-links');
        el.value = (el.value ? el.value + '\n' : '') + e.target.result;
        el.dispatchEvent(new Event('input'));
    };
    reader.readAsText(input.files[0]);
}

// Предфильтр: ловим явных ботов и одиночных юзеров в списке — предлагаем убрать
// до старта. Логика та же что потом даст ignored:not_a_chat на сервере.
function _sniffJunkLinks(text) {
    var lines = (text || '').split(/[\r\n]+/).map(function(s){return s.trim();}).filter(Boolean);
    var bots = [], users = [];
    lines.forEach(function(l) {
        var m = l.match(/^(?:https?:\/\/)?(?:t|telegram)\.me\/([A-Za-z0-9_]{4,})\/?$/i);
        if (!m) return;
        var uname = m[1].toLowerCase();
        if (uname.endsWith('bot')) bots.push(l);
    });
    return {bots: bots, users: users};
}

function startJoin() {
    var taEl = document.getElementById('join-links');
    var links = taEl.value.trim();
    if (!links) { showToast('Введите ссылки на чаты', 'warning'); return; }

    var junk = _sniffJunkLinks(links);
    if (junk.bots.length >= 3) {
        var msg = 'В списке ' + junk.bots.length + ' ссылок на ботов (оканчиваются на _bot). ' +
                  'Подписаться на бота нельзя — они всё равно не пройдут (ignored:not_a_chat).\n\n' +
                  'Убрать их автоматически и продолжить?';
        showConfirmDialog('Убрать ботов из списка?', msg, function() {
            var botSet = {}; junk.bots.forEach(function(b) { botSet[b] = 1; });
            var clean = links.split(/[\r\n]+/).filter(function(l) { return !botSet[l.trim()]; }).join('\n');
            taEl.value = clean;
            taEl.dispatchEvent(new Event('input'));
            _doStartJoin(clean);
        }, function() {
            _doStartJoin(links);
        });
        return;
    }
    _doStartJoin(links);
}

function _doStartJoin(links) {
    API.post('/api/join/load', {text: links}).then(function(d) {
        if (!d.ok) { showToast('Ошибка загрузки ссылок', 'error'); return Promise.reject(); }
        var payload = {ad_config: readAdConfig('join')};
        var capEl = document.getElementById('join-max-per-acc');
        if (capEl) {
            var capVal = parseInt(capEl.value || '0', 10);
            if (!isNaN(capVal) && capVal > 0) payload.max_per_acc = capVal;
        }
        return API.post('/api/join/start', payload);
    }).then(function(d) {
        if (d && d.ok) {
            showToast('Подписка запущена! ' + d.total + ' чатов' + (d.removed > 0 ? ' (убрано ' + d.removed + ' дубликатов)' : ''), 'success');
            document.getElementById('btn-join').disabled = true;
            refreshStatus();
        } else if (d) {
            showToast(d.error || 'Ошибка запуска', 'error');
        }
    }).catch(function(e) { if (e && e.error) showToast(e.error, 'error'); });
}

// ══════════════════════════════════════════════════════════════════
//  MULTI — несколько аккаунтов
// ══════════════════════════════════════════════════════════════════

var _multiSelected = [];

function loadMultiAccounts() {
    // Just update counter, popup handles selection
    var countEl = document.getElementById('multi-selected-count');
    if (countEl) countEl.textContent = _multiSelected.length + ' выбрано';
    _updateMultiButtons();
    refreshMultiTimings();
}

function refreshMultiTimings() {
    // Снимаем актуальные значения из исходных input-ов модулей и
    // показываем в превью. Если ползунки ещё не инициализированы (settings
    // не успели загрузиться) — берём из API.
    var get = function(id) {
        var el = document.getElementById(id);
        return el ? el.value : null;
    };
    var setText = function(id, v) {
        var el = document.getElementById(id);
        if (el) el.textContent = (v === null || v === undefined || v === '') ? '—' : v;
    };
    setText('mtj-i', get('s-join-interval'));
    setText('mtj-j', get('s-join-jitter'));
    setText('mtj-m', get('s-join-max'));
    setText('mtj-b', get('s-join-break'));
    setText('mtj-bm', get('s-join-break-max'));
    setText('mts-mn', get('s-spam-min'));
    setText('mts-mx', get('s-spam-max'));
    setText('mts-b', get('s-spam-burst'));
    setText('mts-bp', get('s-spam-burst-pause-min'));
    setText('mts-bpm', get('s-spam-burst-pause-max'));
    setText('mtd-mn', get('s-dm-min'));
    setText('mtd-mx', get('s-dm-max'));
    setText('mtd-b', get('s-dm-burst'));
    setText('mtd-bp', get('s-dm-burst-pause-min'));
    setText('mtd-bpm', get('s-dm-burst-pause-max'));
    // Если значений нет (не загружены) — fallback на /api/settings
    if (!get('s-join-interval')) {
        API.get('/api/settings').then(function(s) {
            if (!s) return;
            var jn = s.join || {}, sp = s.spam || {}, dm = s.dm || {};
            setText('mtj-i', jn.min_interval); setText('mtj-j', jn.jitter); setText('mtj-m', jn.max_per_session);
            setText('mtj-b', jn.session_break_min); setText('mtj-bm', jn.session_break_max);
            setText('mts-mn', sp.min_delay); setText('mts-mx', sp.max_delay); setText('mts-b', sp.burst_size);
            setText('mts-bp', sp.burst_pause_min); setText('mts-bpm', sp.burst_pause_max);
            setText('mtd-mn', dm.min_delay); setText('mtd-mx', dm.max_delay); setText('mtd-b', dm.burst_size);
            setText('mtd-bp', dm.burst_pause_min); setText('mtd-bpm', dm.burst_pause_max);
        }).catch(function() {});
    }
}

function toggleMultiAcc(phone, btn) {
    var idx = _multiSelected.indexOf(phone);
    if (idx === -1) {
        _multiSelected.push(phone);
        btn.className = 'btn btn-sm btn-accent';
    } else {
        _multiSelected.splice(idx, 1);
        btn.className = 'btn btn-sm btn-outline';
    }
    _updateMultiButtons();
}

function _updateMultiButtons() {
    var has = _multiSelected.length > 0;
    var el;
    el = document.getElementById('btn-multi-join'); if (el) el.disabled = !has;
    el = document.getElementById('btn-multi-spam'); if (el) el.disabled = !has;
    el = document.getElementById('btn-multi-dm'); if (el) el.disabled = !has;
}

function openMultiAccPopup() {
    API.get('/api/accounts').then(function(d) {
        if (!d.accounts || !d.accounts.length) { showToast('Нет аккаунтов', 'warning'); return; }
        var old = document.getElementById('acc-popup-overlay');
        if (old) old.remove();

        var overlay = document.createElement('div');
        overlay.id = 'acc-popup-overlay';
        overlay.className = 'acc-popup-overlay';
        overlay.onclick = function(e) { if (e.target === overlay) overlay.remove(); };

        var popup = document.createElement('div');
        popup.className = 'acc-popup';
        popup.innerHTML = '<button class="acc-popup-close" onclick="document.getElementById(\'acc-popup-overlay\').remove()"><i class="bi bi-x-lg"></i></button>' +
            '<h3><i class="bi bi-people" style="color:var(--warning);margin-right:6px;"></i>Выбор аккаунтов</h3>';

        // Select all
        var selAll = document.createElement('div');
        selAll.style.cssText = 'padding:8px 12px;margin-bottom:8px;display:flex;align-items:center;gap:8px;cursor:pointer;border-bottom:1px solid var(--border);';
        selAll.innerHTML = '<input type="checkbox" id="multi-select-all" > <span style="font-size:0.8rem;color:var(--text-primary);">Выбрать все (' + d.accounts.length + ')</span>';
        var selAllCb = selAll.querySelector('input');
        selAllCb.onchange = function() {
            var items = popup.querySelectorAll('.multi-acc-cb');
            items.forEach(function(c) { c.checked = selAllCb.checked; });
            popup.querySelectorAll('.acc-popup-item').forEach(function(el) { el.classList.toggle('selected', selAllCb.checked); });
            _syncMultiFromPopup(popup);
        };
        selAll.onclick = function(e) {
            if (e.target === selAllCb) return;
            selAllCb.checked = !selAllCb.checked;
            selAllCb.onchange();
        };
        popup.appendChild(selAll);

        // Count available (not busy, not dead)
        var availableCount = d.accounts.filter(function(a) { return !a.dead && !a.busy; }).length;
        selAll.querySelector('span').textContent = 'Выбрать все (' + availableCount + ' доступно)';

        d.accounts.forEach(function(a) {
            var isBusy = !!a.busy;
            var sel = !isBusy && _multiSelected.indexOf(a.phone) !== -1;
            var dotColor = a.dead ? 'var(--danger)' : isBusy ? 'var(--accent)' : a.available ? 'var(--success)' : 'var(--text-muted)';
            var statusText = a.dead ? 'Мёртвый' : a.frozen ? 'Заморозка' : isBusy ? ('▶ ' + a.busy) : a.available ? 'Готов' : 'Сон';
            var item = document.createElement('div');
            item.className = 'acc-popup-item' + (sel ? ' selected' : '');
            item.style.cssText += 'gap:8px;' + (isBusy ? 'opacity:0.5;' : '');
            item.innerHTML = '<input type="checkbox" class="multi-acc-cb" data-phone="' + a.phone + '" ' + (sel ? 'checked' : '') + (isBusy || a.dead ? ' disabled' : '') + ' >' +
                '<span class="dot" style="background:' + dotColor + ';"></span>' +
                '<span class="phone">' + flagImg(a.flag) + ' +' + a.phone + '</span>' +
                (a.username ? '<span style="font-size:0.7rem;color:var(--accent-light);">@' + escHtml(a.username) + '</span>' : '') +
                '<span class="status-text" style="' + (isBusy ? 'color:var(--accent);font-weight:600;' : '') + '">' + statusText + '</span>';
            if (!isBusy && !a.dead) {
                item.onclick = function(e) {
                    if (e.target.tagName === 'INPUT') return;
                    var cb = item.querySelector('.multi-acc-cb');
                    cb.checked = !cb.checked;
                    item.classList.toggle('selected', cb.checked);
                    _syncMultiFromPopup(popup);
                };
                item.querySelector('.multi-acc-cb').onchange = function() {
                    item.classList.toggle('selected', this.checked);
                    _syncMultiFromPopup(popup);
                };
            }
            popup.appendChild(item);
        });

        // OK button
        var okBtn = document.createElement('button');
        okBtn.className = 'btn btn-accent btn-sm w-100';
        okBtn.style.marginTop = '12px';
        okBtn.innerHTML = '<i class="bi bi-check2"></i> Готово';
        okBtn.onclick = function() { overlay.remove(); };
        popup.appendChild(okBtn);

        overlay.appendChild(popup);
        document.body.appendChild(overlay);
    });
}

function _syncMultiFromPopup(popup) {
    _multiSelected = [];
    popup.querySelectorAll('.multi-acc-cb:checked').forEach(function(cb) {
        _multiSelected.push(cb.dataset.phone);
    });
    _updateMultiButtons();
    var countEl = document.getElementById('multi-selected-count');
    if (countEl) countEl.textContent = _multiSelected.length + ' выбрано';
}

function multiShowSection(id) {
    ['multi-join-section', 'multi-spam-section', 'multi-dm-section'].forEach(function(s) {
        var el = document.getElementById(s);
        if (el) el.style.display = s === id && el.style.display !== 'block' ? 'block' : 'none';
    });
}

function multiJoin() {
    // Берём ссылки из мульти-секции или из таба вступления
    var links = (document.getElementById('multi-join-links') || {}).value || (document.getElementById('join-links') || {}).value || '';
    links = links.trim();
    if (!links) { showToast('Введите ссылки на чаты', 'warning'); return; }
    if (!_multiSelected.length) { showToast('Выберите аккаунты', 'warning'); return; }

    API.post('/api/join/load', {text: links}).then(function(d) {
        if (!d.ok) { showToast('Ошибка загрузки ссылок', 'error'); return Promise.reject(); }
        var payload = {phones: _multiSelected, ad_config: readAdConfig('multi')};
        var capEl = document.getElementById('join-max-per-acc');
        if (capEl) {
            var capVal = parseInt(capEl.value || '0', 10);
            if (!isNaN(capVal) && capVal > 0) payload.max_per_acc = capVal;
        }
        return API.post('/api/multi/join', payload);
    }).then(function(d) {
        if (d && d.ok) {
            showToast('Автоподписка запущена! ' + d.started + ' акков, ' + d.total + ' чатов', 'success');
            refreshStatus();
        } else if (d) {
            showToast(d.error || 'Ошибка', 'error');
        }
    }).catch(function(e) { if (e) showToast('Ошибка: ' + e, 'error'); });
}

function multiSpam() {
    var text = (document.getElementById('multi-spam-text') || {}).value || '';
    if (!text.trim()) { showToast('Введите текст рассылки', 'warning'); return; }
    if (!_multiSelected.length) { showToast('Выберите аккаунты', 'warning'); return; }
    // Лимит «N сообщений с акка» из основного поля tab-spam — по той же
    // логике как DM: значение читается из общего инпута, поэтому
    // одиночный/мульти-режим показывают одну и ту же настройку.
    var capEl = document.getElementById('spam-max-per-acc');
    var capVal = capEl ? parseInt(capEl.value || '0', 10) : 0;
    var payload = {phones: _multiSelected, text: text, ad_config: readAdConfig('multi')};
    if (!isNaN(capVal) && capVal > 0) payload.max_per_acc = capVal;
    API.post('/api/multi/spam', payload).then(function(d) {
        if (d && d.ok) {
            showToast('Рассылка запущена на ' + (d.started || _multiSelected.length) + ' акков', 'success');
            refreshStatus();
        } else if (d) {
            showToast(d.error || 'Ошибка', 'error');
        }
    }).catch(function(e) { showToast('Ошибка: ' + e, 'error'); });
}

function multiDm() {
    var text = (document.getElementById('multi-dm-text') || {}).value || '';
    var targets = (document.getElementById('multi-dm-targets') || {}).value || '';
    if (!text.trim()) { showToast('Введите текст', 'warning'); return; }
    if (!targets.trim()) { showToast('Введите получателей', 'warning'); return; }
    if (!_multiSelected.length) { showToast('Выберите аккаунты', 'warning'); return; }
    // Лимит «N сообщений с акка» из общего поля DM-вкладки. 0 = без лимита.
    var capEl = document.getElementById('dm-max-per-acc');
    var capVal = capEl ? parseInt(capEl.value || '0', 10) : 0;
    var payload = {phones: _multiSelected, text: text, targets: targets, ad_config: readAdConfig('multi')};
    if (!isNaN(capVal) && capVal > 0) payload.max_per_acc = capVal;
    API.post('/api/multi/dm', payload).then(function(d) {
        if (d && d.ok) {
            showToast('ЛС рассылка запущена на ' + (d.started || _multiSelected.length) + ' акков', 'success');
            refreshStatus();
        } else if (d) {
            showToast(d.error || 'Ошибка', 'error');
        }
    }).catch(function(e) { showToast('Ошибка: ' + e, 'error'); });
}

// ══════════════════════════════════════════════════════════════════
//  INLINE ACCOUNT SELECTOR — выбор аккаунта в каждом модуле
// ══════════════════════════════════════════════════════════════════

var _cachedAccounts = null;
var _cachedActive = null;

// Track which selectors are in "all"/"one"/"multi" mode
var _accSelectorMode = {};
// Multi-mode selection per selector id: {id: [phone1, phone2, ...]}
var _accSelectorPicked = {};

function renderAccountSelectors() {
    API.get('/api/accounts').then(function(d) {
        _cachedAccounts = d.accounts || [];
        _cachedActive = d.active;
        var ids = ['acc-selector-spam', 'acc-selector-dm', 'acc-selector-join', 'acc-selector-parser', 'acc-selector-profiles', 'acc-selector-autoreply', 'acc-selector-monitor', 'acc-selector-neuro-comments', 'acc-selector-imitation', 'acc-selector-bulk-fill', 'acc-selector-checker'];
        ids.forEach(function(id) {
            var el = document.getElementById(id);
            if (!el) return;
            if (!_cachedAccounts.length) {
                el.innerHTML = '<span class="acc-label" style="color:var(--danger);">Нет аккаунтов — добавьте в разделе «Аккаунты»</span>';
                return;
            }
            var mode = _accSelectorMode[id] || 'one';
            // Для мониторинга показываем акк из папки «Мониторинг» — иначе
            // селект показывал случайный первый из списка, и юзер думал что
            // софт игнорирует папку. Если в папке несколько — берём первого живого.
            var activeAcc;
            if (id === 'acc-selector-monitor') {
                activeAcc = _cachedAccounts.find(function(a) {
                    return (a.folder || '') === 'Мониторинг' && !a.dead && !a.frozen;
                });
            }
            if (!activeAcc) {
                activeAcc = _cachedAccounts.find(function(a) { return a.phone === _cachedActive; }) || _cachedAccounts[0];
            }
            var dotColor = activeAcc.dead ? 'var(--danger)' : activeAcc.available ? 'var(--success)' : 'var(--text-muted)';

            // Single-account-only modules: no "all" toggle
            // (для массовой работы используется вкладка Мульти)
            var singleOnly = (id === 'acc-selector-spam' || id === 'acc-selector-dm' || id === 'acc-selector-join' || id === 'acc-selector-monitor');
            if (singleOnly) mode = 'one';

            // Mode toggle buttons (hidden for single-only modules)
            var modeHtml = '';
            if (!singleOnly) {
                function _btn(m, icon, label) {
                    var active = (mode === m);
                    return '<button class="btn btn-sm" onclick="setAccSelectorMode(\'' + id + '\',\'' + m + '\')" style="padding:5px 12px;font-size:0.72rem;border:1px solid ' + (active ? 'var(--accent)' : 'var(--border)') + ';color:' + (active ? 'var(--accent-light)' : 'var(--text-muted)') + ';background:' + (active ? '#1a1040' : 'transparent') + ';"><i class="bi ' + icon + '"></i> ' + label + '</button>';
                }
                modeHtml = '<div style="display:flex;gap:4px;margin-right:8px;">' +
                    _btn('one', 'bi-person', 'Один') +
                    _btn('multi', 'bi-check2-square', 'Выбрать') +
                    _btn('all', 'bi-people', 'Все') +
                '</div>';
            }

            if (mode === 'all') {
                el.innerHTML = modeHtml + '<span class="acc-label" style="color:var(--success);font-weight:600;">' + _cachedAccounts.length + ' акков выбрано</span>';
            } else if (mode === 'multi') {
                var picked = _accSelectorPicked[id] || [];
                var label = picked.length ? (picked.length + ' из ' + _cachedAccounts.length + ' выбрано') : 'Нажмите чтобы выбрать';
                var color = picked.length ? 'var(--success)' : 'var(--warning)';
                el.innerHTML = modeHtml +
                    '<button class="acc-chip active" onclick="openAccMultiPopup(\'' + id + '\')">' +
                        '<i class="bi bi-check2-square" style="color:' + color + ';margin-right:4px;"></i>' +
                        '<span style="color:' + color + ';font-weight:600;">' + label + '</span>' +
                        ' <i class="bi bi-chevron-down" style="font-size:0.6rem;margin-left:4px;"></i>' +
                    '</button>';
            } else {
                el.innerHTML = modeHtml +
                    '<button class="acc-chip active" onclick="openAccPopup(\'' + id + '\')">' +
                        '<span class="dot" style="background:' + dotColor + ';"></span>' +
                        flagImg(activeAcc.flag) + ' +' + activeAcc.phone +
                        ' <i class="bi bi-chevron-down" style="font-size:0.6rem;margin-left:4px;"></i>' +
                    '</button>' +
                    '<span class="acc-label" style="margin-left:6px;">' + _cachedAccounts.length + ' акков</span>';
            }
        });
    });
}

function setAccSelectorMode(id, mode) {
    _accSelectorMode[id] = mode;
    renderAccountSelectors();
}

function openAccMultiPopup(selectorId) {
    if (!_cachedAccounts || !_cachedAccounts.length) return;
    var old = document.getElementById('acc-popup-overlay');
    if (old) old.remove();

    if (!_accSelectorPicked[selectorId]) _accSelectorPicked[selectorId] = [];
    var picked = _accSelectorPicked[selectorId];

    var overlay = document.createElement('div');
    overlay.id = 'acc-popup-overlay';
    overlay.className = 'acc-popup-overlay';
    overlay.onclick = function(e) { if (e.target === overlay) overlay.remove(); };

    var popup = document.createElement('div');
    popup.className = 'acc-popup';
    popup.style.maxHeight = '70vh';
    popup.style.overflowY = 'auto';

    var header = '<button class="acc-popup-close" onclick="document.getElementById(\'acc-popup-overlay\').remove()"><i class="bi bi-x-lg"></i></button>' +
        '<h3><i class="bi bi-check2-square" style="color:var(--accent-light);margin-right:6px;"></i>Выбор аккаунтов</h3>' +
        '<div style="display:flex;gap:6px;margin-bottom:8px;">' +
            '<button class="btn btn-outline btn-sm" onclick="_multiPickToggleAll(\'' + selectorId + '\', true)">Все</button>' +
            '<button class="btn btn-outline btn-sm" onclick="_multiPickToggleAll(\'' + selectorId + '\', false)">Снять</button>' +
        '</div>';
    popup.innerHTML = header;

    _cachedAccounts.forEach(function(a) {
        var isChecked = picked.indexOf(a.phone) !== -1;
        var dotColor = a.dead ? 'var(--danger)' : a.available ? 'var(--success)' : 'var(--text-muted)';
        var statusText = a.dead ? 'Мёртвый' : a.frozen ? 'Заморозка' : a.spam_blocked ? 'Спам' : a.available ? 'Готов' : 'Спит';
        var item = document.createElement('div');
        item.className = 'acc-popup-item' + (isChecked ? ' selected' : '');
        item.style.cursor = 'pointer';
        item.innerHTML = '<input type="checkbox" ' + (isChecked ? 'checked' : '') + ' style="margin-right:8px;pointer-events:none;">' +
            '<span class="dot" style="background:' + dotColor + ';"></span>' +
            '<span class="phone">' + flagImg(a.flag) + ' +' + a.phone + '</span>' +
            (a.username ? '<span style="font-size:0.7rem;color:var(--accent-light);">@' + escHtml(a.username) + '</span>' : '') +
            '<span class="status-text">' + statusText + '</span>';
        item.onclick = function() {
            var idx = picked.indexOf(a.phone);
            if (idx === -1) picked.push(a.phone); else picked.splice(idx, 1);
            openAccMultiPopup(selectorId);  // перерисовать
        };
        popup.appendChild(item);
    });

    var footer = document.createElement('div');
    footer.style.cssText = 'margin-top:12px;display:flex;gap:8px;justify-content:flex-end;';
    footer.innerHTML = '<button class="btn btn-accent btn-sm" onclick="document.getElementById(\'acc-popup-overlay\').remove(); renderAccountSelectors();">Готово (' + picked.length + ')</button>';
    popup.appendChild(footer);

    overlay.appendChild(popup);
    document.body.appendChild(overlay);
}

function _multiPickToggleAll(selectorId, selectAll) {
    if (!_accSelectorPicked[selectorId]) _accSelectorPicked[selectorId] = [];
    if (selectAll) {
        _accSelectorPicked[selectorId] = (_cachedAccounts || []).map(function(a) { return a.phone; });
    } else {
        _accSelectorPicked[selectorId] = [];
    }
    openAccMultiPopup(selectorId);
}

function getSelectorPhones(id) {
    var mode = _accSelectorMode[id] || 'one';
    if (mode === 'all') {
        return (_cachedAccounts || []).filter(function(a) { return a.available; }).map(function(a) { return a.phone; });
    }
    if (mode === 'multi') {
        return (_accSelectorPicked[id] || []).slice();
    }
    return _cachedActive ? [_cachedActive] : [];
}

function openAccPopup(selectorId) {
    if (!_cachedAccounts || !_cachedAccounts.length) return;
    // Remove existing popup
    var old = document.getElementById('acc-popup-overlay');
    if (old) old.remove();

    var overlay = document.createElement('div');
    overlay.id = 'acc-popup-overlay';
    overlay.className = 'acc-popup-overlay';
    overlay.onclick = function(e) { if (e.target === overlay) overlay.remove(); };

    var popup = document.createElement('div');
    popup.className = 'acc-popup';
    popup.innerHTML = '<button class="acc-popup-close" onclick="document.getElementById(\'acc-popup-overlay\').remove()"><i class="bi bi-x-lg"></i></button>' +
        '<h3><i class="bi bi-phone" style="color:var(--accent-light);margin-right:6px;"></i>Выбор аккаунта</h3>';

    _cachedAccounts.forEach(function(a) {
        var isActive = a.phone === _cachedActive;
        var dotColor = a.dead ? 'var(--danger)' : a.available ? 'var(--success)' : 'var(--text-muted)';
        var statusText = a.dead ? 'Мёртвый' : a.frozen ? 'Заморозка' : a.spam_blocked ? 'Спам' : a.available ? 'Готов' : 'Спит';
        var item = document.createElement('div');
        item.className = 'acc-popup-item' + (isActive ? ' selected' : '');
        item.innerHTML = '<span class="dot" style="background:' + dotColor + ';"></span>' +
            '<span class="phone">' + flagImg(a.flag) + ' +' + a.phone + '</span>' +
            (a.username ? '<span style="font-size:0.7rem;color:var(--accent-light);">@' + escHtml(a.username) + '</span>' : '') +
            '<span class="status-text">' + statusText + '</span>';
        item.onclick = function() {
            pickAccount(a.phone);
            overlay.remove();
        };
        popup.appendChild(item);
    });

    overlay.appendChild(popup);
    document.body.appendChild(overlay);
}

function pickAccount(phone) {
    API.post('/api/accounts/select', {phone: phone}).then(function(d) {
        if (d.ok) {
            renderAccountSelectors();
            loadAccounts();
            // Reload parser chats if on parser tab
            if (_currentTab === 'parser') loadParserChats();
        }
    });
}

// ══════════════════════════════════════════════════════════════════
//  ACCOUNTS — управление аккаунтами
// ══════════════════════════════════════════════════════════════════

var _allAccounts = [];
var _accFilter = 'all';

function loadAccounts() {
    loadAccountTags();
    API.get('/api/accounts').then(function(d) {
        var el = document.getElementById('accounts-list');
        var total = document.getElementById('acc-total');
        if (!d.accounts || !d.accounts.length) {
            var _path = d.accounts_dir || 'папка Аккаунты/';
            var _disk = d.disk_sessions_count || 0;
            var _hint = '';
            if (_disk > 0) {
                _hint = '<div style="margin-top:8px;color:#f87171;font-size:0.78rem;"><i class="bi bi-exclamation-triangle-fill"></i> На диске найдено ' + _disk + ' .session файлов, но они не подгрузились. Возможно имена файлов содержат «+» или пробелы — переименуй в «ЧИСЛО.session» и нажми «Обновить».</div>';
            } else {
                _hint = '<div style="margin-top:8px;color:var(--text-muted);font-size:0.78rem;">Добавь аккаунт через форму выше или закинь .session+.json файлы в папку и нажми «Обновить».</div>';
            }
            el.innerHTML = '<tr><td colspan="9" style="padding:0;">' +
                '<div class="empty-state">' +
                    '<i class="bi bi-inbox empty-state__icon"></i>' +
                    '<div class="empty-state__title">Аккаунтов пока нет</div>' +
                    '<div class="empty-state__text"><i class="bi bi-folder2-open"></i> Папка: <code style="background:var(--bg-card-hover);padding:2px 6px;border-radius:4px;font-size:0.7rem;">' + escHtml(_path) + '</code></div>' +
                    _hint +
                '</div>' +
            '</td></tr>';
            if (total) total.textContent = '0';
            _allAccounts = [];
            updateAccStats();
            return;
        }
        _allAccounts = d.accounts.map(function(a) {
            a._active = a.phone === d.active;
            // Compute spam status
            var sb = (a.spamblock || '').toLowerCase();
            a._isPerma = sb.indexOf('permanent') !== -1 || sb.indexOf('вечн') !== -1;
            a._isTemp = sb.indexOf('temporary') !== -1 || sb.indexOf('времен') !== -1;
            a._isSpam = a.spam_blocked || a._isPerma || a._isTemp || (sb && sb !== 'free' && sb !== 'none' && sb !== 'no' && sb !== '');
            // Age
            a._ageText = '';
            if (a.register_time) {
                var ageDays = Math.floor((Date.now()/1000 - a.register_time) / 86400);
                if (ageDays > 365) a._ageText = Math.floor(ageDays/365) + 'г ' + Math.floor((ageDays%365)/30) + 'мес';
                else if (ageDays > 30) a._ageText = Math.floor(ageDays/30) + ' мес';
                else a._ageText = ageDays + ' д';
            }
            return a;
        });
        updateAccStats();
        renderAccountsTable();
    }).catch(function() {});
}

// ── Filters: auto-generated from real folders on disk ──────────────────────
// Pretty names for known folder names (both cyrillic and latin)
var _folderLabels = {
    'В работе': 'Для работы', 'Active': 'Для работы',
    'Мёртвые': 'Мёртвые', 'Dead': 'Мёртвые',
    'Заморозка': 'Заморозка', 'Frozen': 'Заморозка',
    'В спам': 'В спаме', 'Spam': 'В спаме',
    'Вечный спам': 'Вечный спам', 'Permanent_spam': 'Вечный спам',
    'Прогрев': 'Прогрев', 'Warmup': 'Прогрев',
    'Ферма': 'Ферма', 'Farm': 'Ферма',
    'Архив': 'Архив', 'Archive': 'Архив',
};
// Known folder order (first = top). Мёртвые исключены — не имеют смысла в фильтрах
var _folderOrder = ['В работе', 'Active', 'Прогрев', 'Warmup', 'Ферма', 'Farm',
                     'Заморозка', 'Frozen',
                     'В спам', 'Spam', 'Вечный спам', 'Permanent_spam', 'Архив', 'Archive'];
// Папки которые НЕ показываем как chips (операционные/технические)
var _folderHidden = ['После переавторизации', 'После проверки', 'После инвайта', 'После фермы',
                     'Неверный формат', 'Конвертер tdata → session', 'Конвертер session → tdata',
                     'Мёртвые', 'Dead'];

var _dynamicFilters = [{ id: 'all', name: 'Все аккаунты', type: 'builtin' }];

function _buildFiltersFromFolders(folders) {
    var filters = [{ id: 'all', name: 'Все аккаунты', type: 'builtin' }];
    var added = {};
    // First add known folders in order
    _folderOrder.forEach(function(fname) {
        if (added[_folderLabels[fname]]) return;
        var found = folders.find(function(f) { return f.name === fname; });
        if (found) {
            var label = _folderLabels[fname] || fname;
            added[label] = true;
            filters.push({ id: 'folder_' + fname, name: label, folder: fname, type: 'builtin' });
        }
    });
    // Then add any remaining folders (custom user folders) кроме скрытых
    folders.forEach(function(f) {
        if (_folderHidden.indexOf(f.name) !== -1) return;
        var label = _folderLabels[f.name] || f.name;
        if (added[label]) return;
        added[label] = true;
        filters.push({ id: 'folder_' + f.name, name: label, folder: f.name, type: 'custom' });
    });
    _dynamicFilters = filters;
}

function _loadFiltersFromServer() {
    API.get('/api/folders').then(function(d) {
        if (d.folders && d.folders.length) {
            _buildFiltersFromFolders(d.folders);
            updateAccStats();
        }
    }).catch(function(){});
}
// Run on load
document.addEventListener('DOMContentLoaded', function() { setTimeout(_loadFiltersFromServer, 500); });

function _getAllFilters() {
    return _dynamicFilters;
}

function _countForFilter(f) {
    if (f.id === 'all') return _allAccounts.length;
    if (f.folder) {
        return _allAccounts.filter(function(a) { return (a.folder || '') === f.folder; }).length;
    }
    return 0;
}

function updateAccStats() {
    var row = document.getElementById('acc-stats-row');
    if (!row) return;
    var filters = _getAllFilters();
    var html = '';
    filters.forEach(function(f) {
        var count = _countForFilter(f);
        var isActive = _accFilter === f.id;
        var canDelete = f.type !== 'builtin';
        html += '<div class="acc-stat-item' + (isActive ? ' active' : '') + '" data-filter="' + f.id + '" style="position:relative;">' +
            '<span class="acc-stat-value">' + count + '</span>' +
            '<span class="acc-stat-label">' + escHtml(f.name) + '</span>' +
            (canDelete ? '<button onclick="event.stopPropagation();removeCustomFilter(\'' + f.id + '\')" style="position:absolute;top:4px;right:6px;background:none;border:none;color:var(--danger);cursor:pointer;font-size:0.6rem;padding:2px;opacity:0.6;" title="Удалить фильтр"><i class="bi bi-x-lg"></i></button>' : '') +
        '</div>';
    });
    // Add button
    html += '<div class="acc-stat-item" onclick="showAddFilterPopup()" style="cursor:pointer;display:flex;align-items:center;justify-content:center;border-style:dashed;opacity:0.6;" title="Добавить фильтр">' +
        '<i class="bi bi-plus-lg" style="font-size:1.2rem;color:var(--accent-light);"></i>' +
    '</div>';
    row.innerHTML = html;
    var total = document.getElementById('acc-total');
    if (total) total.textContent = _allAccounts.length;
}

function setAccFilter(filter) {
    _accFilter = filter;
    updateAccStats();
    renderAccountsTable();
}

// Init stats row click handlers
document.addEventListener('DOMContentLoaded', function() {
    var row = document.getElementById('acc-stats-row');
    if (row) row.addEventListener('click', function(e) {
        var del = e.target.closest('button');
        if (del) return;
        var item = e.target.closest('.acc-stat-item');
        if (item && item.dataset.filter) setAccFilter(item.dataset.filter);
    });
});

function showAddFilterPopup() {
    var old = document.getElementById('filter-popup-overlay');
    if (old) old.remove();

    var overlay = document.createElement('div');
    overlay.id = 'filter-popup-overlay';
    overlay.className = 'acc-popup-overlay';
    overlay.onclick = function(e) { if (e.target === overlay) overlay.remove(); };

    var popup = document.createElement('div');
    popup.className = 'acc-popup';
    popup.style.minWidth = '320px';
    popup.innerHTML = '<button class="acc-popup-close" onclick="document.getElementById(\'filter-popup-overlay\').remove()"><i class="bi bi-x-lg"></i></button>' +
        '<h3><i class="bi bi-funnel" style="color:var(--accent-light);margin-right:6px;"></i>Добавить папку</h3>' +
        '<label class="form-label">Существующая папка</label>' +
        '<select class="form-control mb-2" id="new-filter-folder"><option value="">Загрузка...</option></select>' +
        '<div class="d-flex gap-2 align-center mb-3">' +
            '<span class="text-xs text-muted">или создать</span>' +
            '<input type="text" class="form-control" id="new-filter-folder-custom" placeholder="Имя новой папки" style="flex:1;">' +
        '</div>' +
        '<button class="btn btn-accent btn-sm" onclick="addCustomFilter()"><i class="bi bi-plus"></i> Добавить</button>';
    overlay.appendChild(popup);
    document.body.appendChild(overlay);

    // Load folders from API
    API.get('/api/folders').then(function(d) {
        var sel = document.getElementById('new-filter-folder');
        if (!sel) return;
        var html = '<option value="">-- выберите папку --</option>';
        (d.folders || []).forEach(function(f) {
            html += '<option value="' + escHtml(f.name) + '">' + escHtml(f.name) + ' (' + f.count + ' акков)</option>';
        });
        sel.innerHTML = html;
    });
}

function addCustomFilter() {
    var folder = document.getElementById('new-filter-folder').value || document.getElementById('new-filter-folder-custom').value.trim();
    if (!folder) { showToast('Выберите или введите имя папки', 'warning'); return; }

    var customName = document.getElementById('new-filter-folder-custom').value.trim();
    var finish = function() {
        document.getElementById('filter-popup-overlay').remove();
        _loadFiltersFromServer();
        showToast('Папка "' + folder + '" добавлена', 'success');
    };

    if (customName) {
        API.post('/api/folders/create', { name: customName }).then(function(d) {
            if (d.ok) finish();
            else showToast(d.error || 'Ошибка', 'error');
        }).catch(function() { showToast('Ошибка создания папки', 'error'); });
    } else {
        finish();
    }
}

function removeCustomFilter(id) {
    // Only remove from UI — folder stays on disk
    if (_accFilter === id) _accFilter = 'all';
    _dynamicFilters = _dynamicFilters.filter(function(f) { return f.id !== id; });
    updateAccStats();
    renderAccountsTable();
}

function filterAccountsTable() {
    renderAccountsTable();
}

function copyPhone(ev, phone) {
    try { ev.stopPropagation(); } catch (_) {}
    var txt = '+' + phone;
    try {
        navigator.clipboard.writeText(txt).then(function() {
            showToast('Скопировано: ' + txt, 'success');
        }).catch(function() {
            // Fallback для старых WebView
            var ta = document.createElement('textarea');
            ta.value = txt; document.body.appendChild(ta); ta.select();
            try { document.execCommand('copy'); showToast('Скопировано: ' + txt, 'success'); }
            catch (_) { showToast('Не удалось скопировать', 'warning'); }
            document.body.removeChild(ta);
        });
    } catch (e) { showToast('Не удалось скопировать', 'warning'); }
}

// Теги по аккаунтам {phone: ["tag1","tag2"]}
var _accountTags = {};
var _allAccountTags = [];

function loadAccountTags() {
    return API.get('/api/account_tags').then(function(d) {
        if (d && d.ok) {
            _accountTags = d.tags || {};
            _allAccountTags = d.all_tags || [];
            var sel = document.getElementById('acc-tag-filter');
            if (sel) {
                var cur = sel.value;
                sel.innerHTML = '<option value="">Все теги</option>' +
                    _allAccountTags.map(function(t){return '<option value="'+escHtml(t)+'">'+escHtml(t)+'</option>'}).join('');
                sel.value = cur;
            }
        }
    });
}

function renderAccountTagsCell(phone) {
    var tags = _accountTags[phone] || [];
    var palette = ['#6d5cc7','#22c55e','#f59e0b','#3b82f6','#ef4444','#ec4899'];
    var chips = tags.slice(0,3).map(function(t,i) {
        return '<span class="badge" style="background:'+palette[i%palette.length]+'22;color:'+palette[i%palette.length]+';font-size:.62rem;padding:1px 6px;border-radius:3px;">'+escHtml(t)+'</span>';
    }).join(' ');
    if (tags.length > 3) chips += ' <span style="font-size:.62rem;color:var(--text-muted)">+'+(tags.length-3)+'</span>';
    return '<div style="display:flex;gap:3px;flex-wrap:wrap;align-items:center;">' +
        (chips || '<span style="font-size:.6rem;color:var(--text-muted)">—</span>') +
        ' <button onclick="editAccountTags(\''+phone+'\',event)" class="btn btn-outline btn-sm" style="padding:1px 5px;font-size:.6rem;" title="Редактировать теги"><i class="bi bi-tags"></i></button>' +
        '</div>';
}

function editAccountTags(phone, e) {
    if (e) e.stopPropagation();
    var current = (_accountTags[phone] || []).join(', ');
    var suggestions = _allAccountTags.length ? '\n\nСуществующие: ' + _allAccountTags.join(', ') : '';
    var raw = prompt('Теги для +' + phone + ' (через запятую, до 6):' + suggestions, current);
    if (raw === null) return;
    var tags = raw.split(',').map(function(s){return s.trim()}).filter(function(s){return s.length});
    API.post('/api/account_tags/set', {phone: phone, tags: tags}).then(function(d) {
        if (d.ok) {
            _accountTags[phone] = d.tags || [];
            loadAccountTags().then(function(){ renderAccountsTable(); });
            toast('Теги сохранены');
        } else {
            toast('Ошибка: ' + (d.error || ''));
        }
    }).catch(function(err){ toast('Ошибка: ' + err.message); });
}

function renderAccountsTable() {
    var el = document.getElementById('accounts-list');
    var search = (document.getElementById('acc-search') || {}).value || '';
    search = search.toLowerCase().trim();
    var tagFilter = (document.getElementById('acc-tag-filter') || {}).value || '';

    var filtered = _allAccounts.filter(function(a) {
        // "all" shows everything
        if (_accFilter === 'all') { /* no folder filter */ }
        else {
            // Find filter (builtin or custom) and match by folder
            var allFilters = _getAllFilters();
            var activeFilter = allFilters.find(function(f) { return f.id === _accFilter; });
            if (activeFilter && activeFilter.folder) {
                if ((a.folder || '') !== activeFilter.folder) return false;
            }
        }
        // Filter by search
        if (search) {
            var hay = (a.phone + ' ' + (a.first_name || '') + ' ' + (a.username || '')).toLowerCase();
            if (hay.indexOf(search) === -1) return false;
        }
        // Filter by tag
        if (tagFilter) {
            var tags = _accountTags[a.phone] || [];
            if (tags.indexOf(tagFilter) === -1) return false;
        }
        return true;
    });

    var shown = document.getElementById('acc-shown');
    if (shown) shown.textContent = filtered.length;

    if (!filtered.length) {
        el.innerHTML = '<tr><td colspan="9" style="padding:0;">' +
            '<div class="empty-state">' +
                '<i class="bi bi-phone empty-state__icon"></i>' +
                '<div class="empty-state__title">' + (search || tagFilter ? 'Ничего не найдено' : 'Аккаунтов пока нет') + '</div>' +
                '<div class="empty-state__text">' + (search || tagFilter ? 'Попробуйте изменить поиск или сбросить фильтр.' : 'Загрузите .session-файл аккаунта чтобы начать работу.') + '</div>' +
            '</div>' +
        '</td></tr>';
        return;
    }

    el.innerHTML = filtered.map(function(a, i) {
        var flag = flagImg(a.flag);
        var cc = a.flag ? a.flag.toUpperCase() : '';

        // Status badge (badge-soft — единый стиль для всех вариантов).
        // «Мёртвый» — только если это подтверждённая Telegram'ом смерть (session_revoked,
        // auth_key_duplicated, user_banned, user_deactivated). Транзиентные ошибки connect
        // или загрузка из state без проверки → «Не проверялся» вместо «Мёртвый», иначе
        // пользователь думает что новый акк сразу умер хотя на самом деле жив.
        var CONFIRMED_DEAD = ['session_revoked','auth_key_duplicated','authkeyrevokedsilent',
            'authkeyunregistered','sessionrevoked','user_banned','user_deactivated'];
        var isReallyDead = a.dead && a.dead_reason && CONFIRMED_DEAD.indexOf(String(a.dead_reason).toLowerCase()) !== -1;

        var statusBadge = '';
        if (isReallyDead) statusBadge = '<span class="badge-soft badge-soft--danger" title="' + escHtml(a.dead_reason || '') + '">Мёртвый</span>';
        else if (a.frozen) statusBadge = '<span class="badge-soft badge-soft--info">Заморозка</span>';
        else if (!a.verified && !a._isPerma && !a._isTemp && !a._isSpam && !a.floodwait)
            statusBadge = '<span class="badge-soft badge-soft--muted" title="Аккаунт ещё не подключался успешно — нажми «Проверить спам» чтобы валидировать">Не проверялся</span>';
        else if (a._isPerma) statusBadge = '<span class="badge-soft badge-soft--danger">Вечный спам</span>';
        else if (a._isTemp || a._isSpam) statusBadge = '<span class="badge-soft badge-soft--warning">Со спамблоком</span>';
        else if (a.floodwait) statusBadge = '<span class="badge-soft badge-soft--warning" title="Флуд-блок от Telegram">Флуд ' + escHtml(a.floodwait) + '</span>';
        else statusBadge = '<span class="badge-soft badge-soft--success"><span class="status-dot status-dot--running" style="margin-right:0;"></span>Без спама</span>';

        // Busy module — пульсирующий индикатор работающей задачи.
        var busyBadge = '';
        if (a.busy) busyBadge = '<span class="badge-soft badge-soft--accent" style="margin-left:4px;"><span class="status-dot status-dot--running" style="margin-right:0;"></span>' + escHtml(a.busy) + '</span>';

        // Proxy badge убран из таблицы аккаунтов — клиент просил,
        // прокси видны в отдельной вкладке «Прокси».

        // Spam info short
        var spamShort = '';
        if (a._isPerma) spamShort = '<span style="color:var(--danger);font-size:0.65rem;">Вечный</span>';
        else if (a._isSpam) spamShort = '<span style="color:var(--warning);font-size:0.65rem;">Временный</span>';
        else if (!a.dead) spamShort = '<span style="color:var(--success);font-size:0.65rem;">Чисто</span>';

        var nameStr = '';
        if (a.first_name) nameStr += escHtml(a.first_name);
        if (a.username) nameStr += (nameStr ? '<br>' : '') + '<span style="color:var(--accent-light);font-size:0.7rem;">@' + escHtml(a.username) + '</span>';

        var rowStyle = a._active ? 'background:var(--bg-card-hover);' : '';

        return '<tr onclick="selectAccount(\'' + a.phone + '\')" style="cursor:pointer;' + rowStyle + '" class="acc-row" data-phone="' + a.phone + '">' +
            '<td style="padding:4px 6px;"><input type="checkbox" class="acc-check" value="' + a.phone + '" onclick="event.stopPropagation()" ></td>' +
            '<td style="color:var(--text-muted);font-size:0.68rem;padding:4px 6px;">' + (i+1) + '</td>' +
            '<td style="padding:4px 6px;white-space:nowrap;">' +
                (a.flag ? flagImg(a.flag) + ' ' : '') +
                '<span class="acc-phone-copy" onclick="copyPhone(event, \'' + a.phone + '\')" title="Кликни чтобы скопировать" style="font-weight:600;font-size:0.77rem;cursor:copy;user-select:all;">+' + a.phone + '</span>' +
                (a.is_premium ? ' <span style="color:#fbbf24;font-size:0.7rem;" title="Premium"><i class="bi bi-star-fill"></i></span>' : '') +
                '</td>' +
            '<td style="padding:4px 6px;">' + (cc ? flag + ' <span style="font-size:0.68rem;color:var(--text-muted);">' + cc + '</span>' : '') + '</td>' +
            '<td style="padding:4px 6px;">' + statusBadge + busyBadge + '</td>' +
            '<td style="padding:4px 6px;"><span style="font-size:0.68rem;color:var(--text-muted);">' + (a._ageText || '—') + '</span></td>' +
            '<td style="font-size:0.72rem;padding:4px 6px;">' + (nameStr || '<span style="color:var(--text-muted);">—</span>') + '</td>' +
            '<td style="padding:4px 6px;">' + renderAccountTagsCell(a.phone) + '</td>' +
            '<td style="text-align:right;padding:4px 6px;">' +
                '<div style="display:flex;gap:3px;justify-content:flex-end;">' +
                    '<button onclick="checkSpam(\'' + a.phone + '\',event)" class="btn btn-outline btn-sm" style="padding:2px 6px;font-size:0.6rem;" title="Проверить спам"><i class="bi bi-shield-check"></i></button>' +
                    '<button onclick="viewAccount(\'' + a.phone + '\',event)" class="btn-tg-open btn-sm" title="Открыть ТГ в вебе"><i class="bi bi-telegram"></i><span class="btn-tg-open-label">Открыть ТГ в вебе</span></button>' +
                    '<button onclick="deleteAccount(\'' + a.phone + '\',event)" class="btn btn-outline btn-sm btn-acc-del" style="padding:2px 6px;font-size:0.6rem;" title="Удалить аккаунт"><i class="bi bi-trash"></i></button>' +
                '</div>' +
            '</td>' +
        '</tr>';
    }).join('');
}

function toggleAccAdd() {
    var body = document.getElementById('acc-add-body');
    var chev = document.getElementById('acc-add-chevron');
    if (body.style.display === 'none') {
        body.style.display = 'block';
        if (chev) chev.style.transform = 'rotate(180deg)';
    } else {
        body.style.display = 'none';
        if (chev) chev.style.transform = '';
    }
}

function toggleAllAccChecks(master) {
    document.querySelectorAll('.acc-check').forEach(function(c) { c.checked = master.checked; });
    updateBulkActionsBar();
}

function _getCheckedAccPhones() {
    var phones = [];
    document.querySelectorAll('.acc-check:checked').forEach(function(c) {
        if (c.value) phones.push(c.value);
    });
    return phones;
}

function updateBulkActionsBar() {
    // Видимость кнопки «Удалить выделенные» + текст «Проверить спам»
    // зависят от числа отмеченных галок.
    var phones = _getCheckedAccPhones();
    var n = phones.length;
    var delBtn = document.getElementById('btn-delete-selected');
    var delCnt = document.getElementById('btn-delete-selected-count');
    if (delBtn) delBtn.style.display = n > 0 ? '' : 'none';
    if (delCnt) delCnt.textContent = n;
    // «Проверить спам» — переименовываем когда выделены конкретные акки.
    var spamBtn = document.getElementById('btn-check-all-spam');
    if (spamBtn && spamBtn.dataset.running !== '1') {
        spamBtn.innerHTML = (n > 0)
            ? '<i class="bi bi-shield-check"></i> Проверить выделенные (' + n + ')'
            : '<i class="bi bi-shield-check"></i> Проверить спам';
    }
}

// При клике по любой строке-чекбоксу — пересчитываем bulk-actions.
// Делегируем чтобы не привязываться к каждой строке отдельно после loadAccounts().
document.addEventListener('change', function(e) {
    if (e.target && e.target.classList && e.target.classList.contains('acc-check')) {
        updateBulkActionsBar();
    }
});

function selectAccount(phone) {
    API.post('/api/accounts/select', {phone: phone}).then(function(d) {
        if (d.ok) {
            // тихо, без toast
            loadAccounts();
        }
    });
}

// ══════════════════════════════════════════════════════════════════
//  INVITE — массовый инвайт юзеров в чат/канал
// ══════════════════════════════════════════════════════════════════

var _invitePollTimer = null;

var _inviteSelectedPhones = [];

function openInviteAccPopup() {
    API.get('/api/accounts').then(function(d) {
        if (!d.accounts || !d.accounts.length) { showToast('Нет аккаунтов', 'warning'); return; }
        var old = document.getElementById('acc-popup-overlay');
        if (old) old.remove();

        var overlay = document.createElement('div');
        overlay.id = 'acc-popup-overlay';
        overlay.className = 'acc-popup-overlay';
        overlay.onclick = function(e) { if (e.target === overlay) overlay.remove(); };

        var popup = document.createElement('div');
        popup.className = 'acc-popup';
        popup.innerHTML = '<button class="acc-popup-close" onclick="document.getElementById(\'acc-popup-overlay\').remove()"><i class="bi bi-x-lg"></i></button>' +
            '<h3><i class="bi bi-people" style="color:var(--accent-light);margin-right:6px;"></i>Выбор акков-инвайтеров</h3>';

        var availableCount = d.accounts.filter(function(a) { return !a.dead && !a.frozen; }).length;
        var selAll = document.createElement('div');
        selAll.style.cssText = 'padding:8px 12px;margin-bottom:8px;display:flex;align-items:center;gap:8px;cursor:pointer;border-bottom:1px solid var(--border);';
        selAll.innerHTML = '<input type="checkbox" id="invite-select-all"> <span style="font-size:0.8rem;">Выбрать всех живых (' + availableCount + ')</span>';
        var selAllCb = selAll.querySelector('input');
        selAllCb.onchange = function() {
            popup.querySelectorAll('.invite-acc-cb').forEach(function(c) {
                if (!c.disabled) c.checked = selAllCb.checked;
            });
            popup.querySelectorAll('.acc-popup-item').forEach(function(el) {
                if (!el.classList.contains('disabled')) el.classList.toggle('selected', selAllCb.checked);
            });
            _syncInviteFromPopup(popup);
        };
        selAll.onclick = function(e) {
            if (e.target === selAllCb) return;
            selAllCb.checked = !selAllCb.checked;
            selAllCb.onchange();
        };
        popup.appendChild(selAll);

        d.accounts.forEach(function(a) {
            var unavailable = a.dead || a.frozen;
            var sel = !unavailable && _inviteSelectedPhones.indexOf(a.phone) !== -1;
            var dotColor = a.dead ? 'var(--danger)' : a.frozen ? '#4a8fd4' : a.busy ? 'var(--accent)' : 'var(--success)';
            var statusText = a.dead ? 'Мёртвый' : a.frozen ? 'Заморозка' : a.busy ? ('▶ ' + a.busy) : 'Готов';
            var item = document.createElement('div');
            item.className = 'acc-popup-item' + (sel ? ' selected' : '') + (unavailable ? ' disabled' : '');
            item.style.cssText += 'gap:8px;' + (unavailable ? 'opacity:0.5;' : '');
            item.innerHTML = '<input type="checkbox" class="invite-acc-cb" data-phone="' + a.phone + '"' + (sel ? ' checked' : '') + (unavailable ? ' disabled' : '') + '>' +
                '<span class="dot" style="background:' + dotColor + ';"></span>' +
                '<span class="phone">' + flagImg(a.flag) + ' +' + a.phone + '</span>' +
                (a.username ? '<span style="font-size:0.7rem;color:var(--accent-light);">@' + escHtml(a.username) + '</span>' : '') +
                '<span class="status-text">' + statusText + '</span>';
            if (!unavailable) {
                item.onclick = function(e) {
                    if (e.target.tagName === 'INPUT') return;
                    var cb = item.querySelector('.invite-acc-cb');
                    cb.checked = !cb.checked;
                    item.classList.toggle('selected', cb.checked);
                    _syncInviteFromPopup(popup);
                };
                item.querySelector('.invite-acc-cb').onchange = function() {
                    item.classList.toggle('selected', this.checked);
                    _syncInviteFromPopup(popup);
                };
            }
            popup.appendChild(item);
        });

        var okBtn = document.createElement('button');
        okBtn.className = 'btn btn-accent btn-sm w-100';
        okBtn.style.marginTop = '12px';
        okBtn.innerHTML = '<i class="bi bi-check2"></i> Готово';
        okBtn.onclick = function() { overlay.remove(); renderInviteAccBadges(); };
        popup.appendChild(okBtn);

        overlay.appendChild(popup);
        document.body.appendChild(overlay);
    });
}

function _syncInviteFromPopup(popup) {
    _inviteSelectedPhones = [];
    popup.querySelectorAll('.invite-acc-cb:checked').forEach(function(cb) {
        if (cb.dataset.phone) _inviteSelectedPhones.push(cb.dataset.phone);
    });
    renderInviteAccBadges();
}

function renderInviteAccBadges() {
    var lbl = document.getElementById('invite-acc-count');
    var list = document.getElementById('invite-acc-list');
    if (!lbl || !list) return;
    if (!_inviteSelectedPhones.length) {
        lbl.textContent = 'все живые';
        list.style.display = 'none';
        list.innerHTML = '';
        return;
    }
    lbl.textContent = 'выбрано: ' + _inviteSelectedPhones.length;
    list.style.display = '';
    list.innerHTML = _inviteSelectedPhones.map(function(p) {
        return '<span class="badge" style="background:var(--surface-info-bg);color:var(--accent-light);padding:4px 8px;border-radius:6px;font-size:0.72rem;display:inline-flex;align-items:center;gap:4px;">' +
            '+' + escHtml(p) +
            '<i class="bi bi-x" style="cursor:pointer;font-size:0.85rem;" onclick="removeInviteAcc(\'' + p + '\')"></i>' +
        '</span>';
    }).join('');
}

function removeInviteAcc(phone) {
    var idx = _inviteSelectedPhones.indexOf(phone);
    if (idx !== -1) {
        _inviteSelectedPhones.splice(idx, 1);
        renderInviteAccBadges();
    }
}

function clearInviteAccs() {
    _inviteSelectedPhones = [];
    renderInviteAccBadges();
}

function inviteShowSubtab(name) {
    ['main', 'userdb', 'reports'].forEach(function(n) {
        var t = document.getElementById('inv-tab-' + n);
        if (t) t.style.display = n === name ? '' : 'none';
        var b = document.getElementById('inv-sub-' + n);
        if (b) {
            b.classList.toggle('btn-accent', n === name);
            b.classList.toggle('btn-outline', n !== name);
        }
    });
    if (name === 'userdb') loadInviteUserdb();
    if (name === 'reports') loadInviteReports();
}
function inviteToggleAdminBlock() {
    var sel = document.getElementById('inv-mode');
    var blk = document.getElementById('inv-admin-block');
    if (sel && blk) blk.style.display = sel.value === 'via_admin' ? '' : 'none';
}
function loadInviteSettings() {
    API.get('/api/invite/settings').then(function(d) {
        if (!d.ok) return;
        var setVal = function(id, v) {
            var el = document.getElementById(id);
            if (el && v !== undefined && v !== null) el.value = v;
        };
        var setChk = function(id, v) {
            var el = document.getElementById(id);
            if (el) el.checked = !!v;
        };
        setVal('invite-pause-min', d.pause_min);
        setVal('invite-pause-max', d.pause_max);
        setVal('invite-max-per-acc', d.max_per_acc);
        setVal('invite-flood-cooldown', d.flood_cooldown_min);
        setVal('invite-hourly-limit', d.hourly_limit);
        setVal('invite-stop-on-privacy', d.stop_on_privacy_streak);
        setVal('inv-spamblock-skip', d.spamblock_skip_limit);
        setVal('inv-chargeback', d.chargeback_limit);
        setVal('inv-big-fw', d.big_floodwait_limit);
        setVal('inv-delay-join-min', d.delay_after_join_min);
        setVal('inv-delay-join-max', d.delay_after_join_max);
        setVal('inv-delay-promote-min', d.delay_after_promote_min);
        setVal('inv-delay-promote-max', d.delay_after_promote_max);
        setVal('inv-cascade-seed', d.cascade_seed_count);
        setChk('inv-set-spamblock', d.set_spamblock_in_userdb);
        setChk('inv-skip-chatinvalid', d.skip_chat_invalid);
        setChk('inv-leave-worker', d.leave_after_invite_worker);
        setChk('inv-leave-admin', d.leave_after_invite_admin);
        setChk('inv-archive', d.archive_chat);
        setVal('inv-mode', d.mode);
        setVal('inv-main-admin', d.main_admin_phone);
        var r = d.admin_rights || {};
        setChk('inv-r-changeinfo', r.change_info);
        setChk('inv-r-deletemsg', r.delete_messages);
        setChk('inv-r-banusers', r.ban_users);
        setChk('inv-r-managecall', r.manage_call);
        setChk('inv-r-pin', r.pin_messages);
        setChk('inv-r-anon', r.anonymous);
        setVal('inv-folder-spamblock', d.folder_spamblock);
        setVal('inv-folder-fresh', d.folder_fresh_session);
        setVal('inv-folder-search', d.folder_search_limit);
        inviteToggleAdminBlock();
    }).catch(function() {});
    inviteShowSubtab('main');
    updateInviteUserdbCount();
    renderInviteAccBadges();
    // Подсчёт целей при изменении textarea
    var ta = document.getElementById('invite-targets');
    if (ta && !ta._counterBound) {
        ta.addEventListener('input', updateInviteTargetsCount);
        ta._counterBound = true;
    }
    updateInviteTargetsCount();
}

function updateInviteTargetsCount() {
    var ta = document.getElementById('invite-targets');
    var lbl = document.getElementById('invite-targets-count');
    if (!ta || !lbl) return;
    var lines = (ta.value || '').split(/[\s,;\n\r]+/).filter(function(x) { return x.trim(); });
    lbl.textContent = lines.length ? (lines.length + ' цел.') : '';
}

function _collectInviteSettings() {
    var v = function(id) { var el = document.getElementById(id); return el ? el.value : ''; };
    var c = function(id) { var el = document.getElementById(id); return el ? el.checked : false; };
    return {
        pause_min: +v('invite-pause-min') || 30,
        pause_max: +v('invite-pause-max') || 70,
        max_per_acc: +v('invite-max-per-acc') || 40,
        hourly_limit: +v('invite-hourly-limit') || 40,
        flood_cooldown_min: +v('invite-flood-cooldown') || 60,
        stop_on_privacy_streak: +v('invite-stop-on-privacy') || 0,
        spamblock_skip_limit: +v('inv-spamblock-skip') || 1,
        chargeback_limit: +v('inv-chargeback') || 999,
        big_floodwait_limit: +v('inv-big-fw') || 9999,
        delay_after_join_min: +v('inv-delay-join-min') || 7,
        delay_after_join_max: +v('inv-delay-join-max') || 15,
        delay_after_promote_min: +v('inv-delay-promote-min') || 5,
        delay_after_promote_max: +v('inv-delay-promote-max') || 12,
        cascade_seed_count: +v('inv-cascade-seed') || 3,
        set_spamblock_in_userdb: c('inv-set-spamblock'),
        skip_chat_invalid: c('inv-skip-chatinvalid'),
        leave_after_invite_worker: c('inv-leave-worker'),
        leave_after_invite_admin: c('inv-leave-admin'),
        archive_chat: c('inv-archive'),
        mode: v('inv-mode') || 'direct',
        main_admin_phone: (v('inv-main-admin') || '').replace(/\D/g, ''),
        admin_rights: {
            change_info: c('inv-r-changeinfo'),
            delete_messages: c('inv-r-deletemsg'),
            ban_users: c('inv-r-banusers'),
            manage_call: c('inv-r-managecall'),
            pin_messages: c('inv-r-pin'),
            anonymous: c('inv-r-anon'),
        },
        folder_spamblock: v('inv-folder-spamblock'),
        folder_fresh_session: v('inv-folder-fresh'),
        folder_search_limit: v('inv-folder-search'),
    };
}
function saveInviteSettings() {
    API.post('/api/invite/settings', _collectInviteSettings()).then(function(d) {
        if (d.ok) showToast('Настройки инвайтера сохранены', 'success');
        else showToast(d.error || 'Ошибка', 'error');
    }).catch(function() { showToast('Ошибка сети', 'error'); });
}

function loadInviteFromParser() {
    API.get('/api/parser/result').then(function(d) {
        if (!d.usernames || !d.usernames.length) {
            showToast('Парсер пуст — сначала запусти парсинг', 'warning'); return;
        }
        var ta = document.getElementById('invite-targets');
        if (!ta) return;
        var items = d.usernames.map(function(u) {
            if (typeof u === 'string') return u;
            return u.username ? '@' + u.username : (u.id ? String(u.id) : '');
        }).filter(function(x) { return x; });
        ta.value = items.join('\n');
        updateInviteTargetsCount();
        showToast('Подставлено ' + items.length + ' юзеров из парсера', 'success');
    }).catch(function() { showToast('Ошибка', 'error'); });
}

function onInviteTargetsFile(e) {
    var f = e.target.files && e.target.files[0];
    if (!f) return;
    var reader = new FileReader();
    reader.onload = function(ev) {
        var ta = document.getElementById('invite-targets');
        if (ta) {
            ta.value = (ev.target.result || '').toString();
            updateInviteTargetsCount();
            showToast('Файл загружен', 'success');
        }
    };
    reader.readAsText(f, 'utf-8');
    e.target.value = '';
}

function startInvite() {
    var chat = ((document.getElementById('invite-chat') || {}).value || '').trim();
    var targets = ((document.getElementById('invite-targets') || {}).value || '').trim();
    if (!chat) { showToast('Укажи целевой чат', 'warning'); return; }
    if (!targets) { showToast('Список целей пуст', 'warning'); return; }
    var phones = (_inviteSelectedPhones || []).slice();
    var payload = Object.assign({
        chat: chat,
        targets: targets,
        phones: phones,
    }, _collectInviteSettings());
    payload.ad_config = readAdConfig('inv');
    API.post('/api/invite/start', payload).then(function(d) {
        if (d.ok) {
            showToast('Инвайт запущен: ' + d.targets + ' целей, ' + d.accs + ' акков', 'success');
            document.getElementById('invite-progress').style.display = '';
            document.getElementById('btn-invite-start').style.display = 'none';
            document.getElementById('btn-invite-stop').style.display = '';
            startInvitePolling();
        } else {
            showToast(d.error || 'Ошибка запуска', 'error');
        }
    }).catch(function(e) { showToast((e && e.message) || 'Ошибка', 'error'); });
}

function stopInvite() {
    API.post('/api/invite/stop', {}).then(function(d) {
        if (d.ok) showToast('Стоп: обработано ' + (d.processed || 0), 'info');
        refreshInviteStatus();
    }).catch(function() { showToast('Ошибка', 'error'); });
}

function startInvitePolling() {
    if (_invitePollTimer) clearInterval(_invitePollTimer);
    _invitePollTimer = setInterval(function() {
        if (_currentTab !== 'invite') {
            clearInterval(_invitePollTimer); _invitePollTimer = null;
            return;
        }
        refreshInviteStatus();
    }, 3000);
    refreshInviteStatus();
}

function refreshInviteStatus() {
    API.get('/api/invite/status').then(function(d) {
        var lbl = document.getElementById('invite-status-label');
        var bar = document.getElementById('invite-bar');
        var counts = document.getElementById('invite-counts');
        var elapsed = document.getElementById('invite-elapsed');
        var log = document.getElementById('invite-log');
        var prog = document.getElementById('invite-progress');
        if (!lbl) return;
        var statusMap = {idle: 'Не запущен', running: 'Идёт', stopped: 'Остановлено', done: 'Готово'};
        lbl.textContent = (statusMap[d.status] || d.status) + ' • ' + (d.processed || 0) + '/' + (d.total || 0) + (d.chat ? ' → ' + d.chat : '');
        if (d.total > 0) bar.style.width = Math.round(d.processed * 100 / d.total) + '%';
        if (bar) {
            if (d.status === 'running') bar.classList.add('is-running');
            else bar.classList.remove('is-running');
        }
        if (counts && d.counts) {
            var c = d.counts;
            counts.innerHTML = [
                '<span style="color:var(--success);"><i class="bi bi-check-circle-fill"></i> ' + (c.invited||0) + ' приглашено</span>',
                '<span style="color:var(--text-muted-strong);">↩ ' + (c.already||0) + ' уже там</span>',
                '<span style="color:var(--warning);"><i class="bi bi-lock-fill"></i> ' + (c.privacy||0) + ' приватность</span>',
                '<span style="color:var(--danger);" title="вечный спам-блок"><i class="bi bi-fire"></i> ' + (c.peer_flood||0) + ' вечный спам</span>',
                '<span style="color:var(--warning);" title="временный флуд-блок"><i class="bi bi-hourglass-split"></i> ' + (c.flood_wait||0) + ' флуд</span>',
                '<span style="color:var(--text-muted-strong);"><i class="bi bi-x-circle"></i> ' + (c.failed||0) + ' ошибок</span>',
                '<span style="color:var(--text-muted-strong);"><i class="bi bi-question-circle"></i> ' + (c.not_found||0) + ' не найдено</span>',
            ].join('');
        }
        if (elapsed) elapsed.textContent = d.elapsed ? Math.floor(d.elapsed/60) + 'м ' + (d.elapsed%60) + 'с' : '—';
        if (log && d.log && d.log.length) {
            log.innerHTML = d.log.map(function(l) { return escHtml(l); }).join('<br>');
            log.scrollTop = log.scrollHeight;
        }
        if (d.status === 'idle') {
            if (prog) prog.style.display = 'none';
        } else {
            if (prog) prog.style.display = '';
        }
        var bs = document.getElementById('btn-invite-start');
        var bp = document.getElementById('btn-invite-stop');
        var be = document.getElementById('btn-invite-export');
        if (d.status === 'running') {
            if (bs) bs.style.display = 'none';
            if (bp) bp.style.display = '';
            if (be) be.style.display = 'none';
        } else {
            if (bs) bs.style.display = '';
            if (bp) bp.style.display = 'none';
            if (be) be.style.display = (d.processed > 0 ? '' : 'none');
        }
    }).catch(function() {});
}

// === Invite User Database (отдельно от monitor contacted) ===
function loadInviteUserdb() {
    var q = (document.getElementById('inv-userdb-search') || {}).value || '';
    var list = document.getElementById('inv-userdb-list');
    if (!list) return;
    list.textContent = 'Загрузка…';
    API.get('/api/invite/userdb_list?q=' + encodeURIComponent(q)).then(function(d) {
        if (!d.ok) { list.innerHTML = '<div style="color:#f87171;">' + escHtml(d.error||'Ошибка') + '</div>'; return; }
        updateInviteUserdbCount(d.total);
        if (!d.items || !d.items.length) {
            list.innerHTML = '<div style="text-align:center;color:var(--text-muted);padding:20px;">База пуста</div>';
            return;
        }
        list.innerHTML = d.items.map(function(it) {
            var tags = (it.tags || []).map(function(t) {
                var color = t === 'spamblock' ? '#f87171' : t === 'invited' ? '#34a853' : 'var(--accent-light)';
                return '<span style="background:' + color + '22;color:' + color + ';border-radius:4px;padding:0 6px;font-size:0.65rem;margin-left:4px;">' + escHtml(t) + '</span>';
            }).join('');
            return '<div style="display:flex;gap:8px;padding:4px 6px;border-bottom:1px solid #1a1a2a;">' +
                '<span style="flex:1;">' + escHtml(it.username ? '@' + it.username : String(it.id)) + tags + '</span>' +
                '<button onclick="_invUserdbRemove(' + it.id + ')" class="btn btn-outline btn-sm" style="padding:0 6px;font-size:0.65rem;color:var(--danger);">×</button>' +
            '</div>';
        }).join('');
    }).catch(function() { list.textContent = 'Ошибка'; });
}
function updateInviteUserdbCount(total) {
    var el = document.getElementById('inv-userdb-cnt');
    if (!el) return;
    if (typeof total === 'number') { el.textContent = total; return; }
    API.get('/api/invite/userdb_count').then(function(d) { if (d.ok) el.textContent = d.count; }).catch(function() {});
}
function _invUserdbRemove(id) {
    API.post('/api/invite/userdb_remove', {ids: [id]}).then(function(d) {
        if (d.ok) { showToast('Удалён ' + id, 'success'); loadInviteUserdb(); }
        else showToast(d.error || 'Ошибка', 'error');
    }).catch(function() {});
}
function loadInviteFromUserdb() {
    API.get('/api/invite/userdb_list?limit=10000').then(function(d) {
        if (!d.ok || !d.items || !d.items.length) { showToast('База пуста', 'warning'); return; }
        var ta = document.getElementById('invite-targets');
        if (!ta) return;
        var lines = d.items.filter(function(it) {
            return !(it.tags || []).includes('invited') && !(it.tags || []).includes('spamblock');
        }).map(function(it) {
            return it.username ? '@' + it.username : String(it.id);
        });
        ta.value = lines.join('\n');
        updateInviteTargetsCount();
        showToast('Подставлено ' + lines.length + ' юзеров из базы', 'success');
    }).catch(function() { showToast('Ошибка', 'error'); });
}
function onInviteUserdbImport(e) {
    var f = e.target.files && e.target.files[0];
    if (!f) return;
    var reader = new FileReader();
    reader.onload = function(ev) {
        var text = (ev.target.result || '').toString();
        API.post('/api/invite/userdb_import', {text: text}).then(function(d) {
            if (d.ok) { showToast('Добавлено ' + d.added + ' (всего ' + d.total + ')', 'success'); loadInviteUserdb(); }
            else showToast(d.error || 'Ошибка', 'error');
        });
    };
    reader.readAsText(f, 'utf-8');
    e.target.value = '';
}
function exportInviteUserdb() {
    API.get('/api/invite/userdb_export').then(function(d) {
        if (d.ok) showToast('Сохранено: ' + d.path, 'success');
        else showToast(d.error || 'Ошибка', 'error');
    }).catch(function() { showToast('Ошибка', 'error'); });
}
function clearInviteUserdb() {
    if (!confirm('Очистить всю базу пользователей инвайтера?')) return;
    API.post('/api/invite/userdb_clear', {}).then(function(d) {
        if (d.ok) { showToast('База очищена', 'success'); loadInviteUserdb(); }
    });
}
function loadInviteReports() {
    var box = document.getElementById('inv-reports-list');
    if (!box) return;
    API.get('/api/invite/reports').then(function(d) {
        if (!d.ok || !d.reports || !d.reports.length) {
            box.innerHTML = '<div style="color:var(--text-muted);text-align:center;padding:20px;">Запусков ещё не было</div>';
            return;
        }
        box.innerHTML = d.reports.map(function(r) {
            return '<div style="background:var(--surface-info-bg);border-radius:8px;padding:10px 14px;margin-bottom:8px;">' +
                '<div style="display:flex;justify-content:space-between;font-size:0.8rem;font-weight:500;margin-bottom:4px;">' +
                    '<span>' + escHtml(r.chat) + '</span>' +
                    '<span style="color:var(--text-muted);font-size:0.7rem;">' + escHtml(r.started_at) + '</span>' +
                '</div>' +
                '<div style="font-size:0.74rem;color:var(--text-muted);">' +
                    '<i class="bi bi-check-circle-fill" style="color:var(--success);"></i> ' + (r.invited||0) +
                    ' &nbsp;<i class="bi bi-lock-fill" style="color:#fbbf24;"></i> ' + (r.privacy||0) +
                    ' &nbsp;<i class="bi bi-fire" style="color:#f97316;"></i> ' + (r.peer_flood||0) +
                    ' &nbsp;<i class="bi bi-x-circle-fill" style="color:var(--danger);"></i> ' + (r.failed||0) +
                '</div>' +
            '</div>';
        }).join('');
    }).catch(function() {});
}

function exportInviteResults() {
    API.get('/api/invite/results').then(function(d) {
        if (!d.results || !Object.keys(d.results).length) {
            showToast('Нет результатов', 'info'); return;
        }
        var lines = Object.keys(d.results).map(function(k) {
            return k + '\t' + d.results[k];
        });
        var blob = new Blob([lines.join('\n')], {type: 'text/plain;charset=utf-8'});
        var url = URL.createObjectURL(blob);
        var a = document.createElement('a');
        a.href = url;
        a.download = 'invite_results_' + new Date().toISOString().replace(/[:.]/g, '-') + '.txt';
        a.click();
        URL.revokeObjectURL(url);
        showToast('Скачано', 'success');
    }).catch(function() { showToast('Ошибка', 'error'); });
}

function toggleSidebar() {
    var collapsed = document.body.classList.toggle('sidebar-collapsed');
    try { localStorage.setItem('sidebar_collapsed', collapsed ? '1' : '0'); } catch(e) {}
}
// Восстанавливаем состояние сайдбара после загрузки страницы
(function _restoreSidebar() {
    try {
        if (localStorage.getItem('sidebar_collapsed') === '1') {
            document.body.classList.add('sidebar-collapsed');
        }
    } catch(e) {}
})();

function deleteAccount(phone, e) {
    if (e) e.stopPropagation();
    showConfirmDialog(
        'Удалить аккаунт +' + phone + '?',
        'Файлы .session и .json будут стёрты с диска без возможности восстановления. Аккаунт можно будет добавить заново только с нуля (вход по коду).',
        function() {
            API.post('/api/accounts/delete', { phone: phone }).then(function(d) {
                if (d.ok) {
                    if (d.errors && d.errors.length) {
                        // Файлы не удалились (антивирус, Telegram-клиент, OS-lock).
                        // Без явного сообщения клиент удивляется почему акк
                        // вернулся после «Обновить» — scan_folder его подхватывает.
                        showToast('Аккаунт убран из списка, но файлы не удалось удалить (' + d.errors.length + ' шт). Перезапустите программу — после рестарта файлы освободятся.', 'warning');
                    } else {
                        showToast('Удалён +' + phone, 'success');
                    }
                    loadAccounts();
                } else {
                    showToast(d.error || 'Не удалось удалить', 'error');
                }
            }).catch(function() { showToast('Ошибка удаления', 'error'); });
        }
    );
}

function deleteAccountsBulk() {
    var phones = [];
    document.querySelectorAll('.acc-check:checked').forEach(function(c) {
        if (c.value) phones.push(c.value);
    });
    if (!phones.length) { showToast('Никто не выбран', 'warning'); return; }
    showConfirmDialog(
        'Удалить ' + phones.length + ' аккаунт(ов)?',
        'Файлы .session и .json для всех выбранных будут стёрты с диска. Действие необратимо.',
        function() {
            API.post('/api/accounts/delete', { phones: phones }).then(function(d) {
                if (d.ok) {
                    showToast('Удалено: ' + d.deleted, 'success');
                    loadAccounts();
                } else {
                    showToast(d.error || 'Ошибка', 'error');
                }
            }).catch(function() { showToast('Ошибка удаления', 'error'); });
        }
    );
}

function viewAccount(phone, e) {
    if (e) e.stopPropagation();
    // Открываем TG-клиент в отдельном окне (pywebview window). Сервер
    // вернёт opened:true если pywebview подхватил, иначе — fallback через
    // window.open в обычном браузере.
    showToast('Открываю Telegram для +' + phone + '…', 'info');
    API.post('/api/tg/open_window', {phone: phone}).then(function(d) {
        if (d.ok) {
            if (!d.opened && d.url) {
                // pywebview не справился — открываем в обычном окне браузера
                window.open(d.url, '_blank', 'width=1200,height=820');
            }
        } else {
            showToast(d.error || 'Не удалось открыть окно', 'error');
        }
    }).catch(function() { showToast('Ошибка открытия', 'error'); });
}

// Старый слайд-панель (мини-просмотр). Оставлен на случай если кто-то
// привык, открывается долгим нажатием кнопки или из консоли.
function viewAccountInline(phone, e) {
    if (e) e.stopPropagation();
    API.post('/api/accounts/select', {phone: phone}).then(function(d) {
        if (d.ok) {
            if (!_tgPanelOpen) toggleTgPanel();
            else loadTgDialogs();
        }
    });
}

function sendCode() {
    var phone = document.getElementById('acc-phone').value.trim();
    if (!phone) { showToast('Введите номер телефона', 'warning'); return; }
    showToast('Отправляю код на ' + phone + '...', 'info');
    API.post('/api/accounts/send_code', {phone: phone}).then(function(d) {
        if (d.ok) {
            document.getElementById('acc-connect-form').style.display = 'none';
            document.getElementById('acc-code-form').style.display = 'block';
            showToast('Код отправлен! Проверьте Telegram', 'success');
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    }).catch(function(e) { showToast(e.error || 'Ошибка отправки кода', 'error'); });
}

function verifyCode() {
    var code = document.getElementById('acc-code').value.trim();
    if (!code) { showToast('Введите код', 'warning'); return; }
    showToast('Проверяю код...', 'info');
    API.post('/api/accounts/verify_code', {code: code}).then(function(d) {
        if (d.ok) {
            showToast('Аккаунт ' + d.phone + ' подключен!', 'success');
            document.getElementById('acc-connect-form').style.display = 'block';
            document.getElementById('acc-code-form').style.display = 'none';
            document.getElementById('acc-phone').value = '';
            document.getElementById('acc-code').value = '';
            loadAccounts();
            refreshStatus();
        } else if (d.needs_2fa) {
            document.getElementById('acc-code-form').style.display = 'none';
            var twofa = document.getElementById('acc-2fa-form');
            if (!twofa) {
                // Create 2FA form dynamically
                var container = document.getElementById('acc-code-form').parentElement;
                var div = document.createElement('div');
                div.id = 'acc-2fa-form';
                div.innerHTML = '<label class="form-label">Пароль 2FA</label>' +
                    '<input type="password" class="form-control mb-2" id="acc-2fa-pass" placeholder="Введите пароль двухфакторной аутентификации">' +
                    '<button class="btn btn-accent btn-sm" onclick="verify2fa()"><i class="bi bi-shield-lock"></i> Подтвердить</button>';
                container.appendChild(div);
            } else {
                twofa.style.display = 'block';
            }
            showToast('Введите пароль 2FA', 'info');
        } else {
            showToast(d.error || 'Неверный код', 'error');
        }
    }).catch(function(e) { showToast(e.error || 'Ошибка', 'error'); });
}

function verify2fa() {
    var pass2fa = (document.getElementById('acc-2fa-pass') || {}).value || '';
    if (!pass2fa) { showToast('Введите пароль', 'warning'); return; }
    API.post('/api/accounts/verify_2fa', { password: pass2fa }).then(function(d) {
        if (d.ok) {
            showToast('Аккаунт ' + d.phone + ' подключен!', 'success');
            document.getElementById('acc-connect-form').style.display = 'block';
            var twofa = document.getElementById('acc-2fa-form');
            if (twofa) twofa.style.display = 'none';
            document.getElementById('acc-phone').value = '';
            loadAccounts();
            refreshStatus();
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    }).catch(function(e) { showToast(e.error || 'Ошибка', 'error'); });
}

// ── Spam Check ─────────────────────────────────────────────────

function checkSpam(phone, e) {
    if (e) e.stopPropagation();
    showToast('Проверяю ' + phone + '...', 'info');
    API.post('/api/accounts/check_spam/' + phone).then(function(d) {
        if (d.ok) {
            // status может быть: 'clean' / 'spam' / 'frozen' / 'dead'.
            // Раньше выводили только blocked/clean, замороженные показывались
            // как «без ограничений» — это и был тот баг с заморозкой.
            if (d.status === 'frozen') {
                showToast('+' + phone + ': Заморозка Telegram (' + (d.note || '') + ')', 'warning');
            } else if (d.status === 'dead') {
                showToast('+' + phone + ': Аккаунт мёртвый (' + (d.note || '') + ')', 'error');
            } else if (d.status === 'spam' || d.blocked) {
                showToast('+' + phone + ': Спамблок (' + (d.scope || '') + ')', 'warning');
            } else {
                showToast('+' + phone + ': Без ограничений', 'success');
            }
            loadAccounts();
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    }).catch(function(e) { showToast('Ошибка: ' + (e.error || e), 'error'); });
}

var _spamCheckTimer = null;

function checkAllSpam() {
    var btn = document.getElementById('btn-check-all-spam');
    // Если уже запущена — превращаем кнопку в стоп
    if (btn && btn.dataset.running === '1') {
        if (!confirm('Остановить проверку?')) return;
        API.post('/api/accounts/check_spam_stop', {}).then(function(d) {
            if (d.ok) showToast('Останавливаю...', 'info');
            else showToast(d.error || 'Не удалось остановить', 'error');
        });
        return;
    }
    // Если есть отмеченные галками — проверяем только их.
    var selected = _getCheckedAccPhones();
    var payload = selected.length ? { phones: selected } : {};
    var label = selected.length ? ('выделенных (' + selected.length + ')') : 'всех аккаунтов';
    showToast('Запускаю проверку ' + label + '...', 'info');
    API.post('/api/accounts/check_spam_all', payload).then(function(d) {
        if (d.ok) {
            showToast('Проверка запущена: ' + d.total + ' акков', 'success');
            if (btn) btn.dataset.running = '1';
            _spamCheckTimer = setInterval(pollSpamCheck, 2000);
        }
    }).catch(function(e) { showToast('Ошибка: ' + (e.error || e), 'error'); });
}

function pollSpamCheck() {
    API.get('/api/accounts/check_spam_status').then(function(d) {
        var btn = document.getElementById('btn-check-all-spam');
        if (d.status === 'running') {
            if (btn) btn.innerHTML = '<i class="bi bi-stop-fill"></i> ' + d.checked + '/' + d.total + ' — Стоп';
        } else if (d.status === 'done' || d.status === 'stopped') {
            clearInterval(_spamCheckTimer);
            if (btn) {
                btn.innerHTML = '<i class="bi bi-shield-check"></i> Проверить все на спам';
                btn.dataset.running = '';
            }
            // Считаем ВСЕ категории чтобы пользователь видел куда делись акки.
            // Раньше показывали только clean+spam — из 29 аккаунтов могло быть
            // «4 без ограничений, 0 в спаме», а 25 замороженных/мёртвых
            // выглядели как «потерянные».
            var clean = 0, spam = 0, frozen = 0, dead = 0, offline = 0, timeout = 0, other = 0, cancelled = 0;
            for (var k in d.results) {
                var v = String(d.results[k] || '');
                if (v === 'clean') clean++;
                else if (v === 'spam') spam++;
                else if (v === 'frozen') frozen++;
                else if (v.indexOf('dead') === 0) dead++;
                else if (v === 'offline') offline++;
                else if (v === 'timeout') timeout++;
                else if (v === 'cancelled') cancelled++;
                else other++;
            }
            var parts = [];
            if (clean) parts.push(clean + ' чистых');
            if (spam) parts.push(spam + ' в спаме');
            if (frozen) parts.push(frozen + ' заморожено');
            if (dead) parts.push(dead + ' мёртвых');
            if (offline) parts.push(offline + ' оффлайн');
            if (timeout) parts.push(timeout + ' таймаут');
            if (other) parts.push(other + ' прочее');
            var summary = parts.length ? parts.join(', ') : 'нет результатов';
            var lvl = (frozen || dead || spam) ? 'warning' : 'success';
            if (d.status === 'stopped') {
                showToast('Остановлено: ' + summary + ' (всего ' + d.total + ')', 'info');
            } else {
                showToast('Проверка завершена: ' + summary, lvl);
            }
            loadAccounts();
        }
    });
}

// ── Session upload (mass) ───────────────────────────────────────

function handleSessionDrop(e) {
    e.preventDefault();
    e.currentTarget.classList.remove('dragover');
    var files = e.dataTransfer.files;
    if (files.length > 0) uploadSessions(files);
}

function uploadSessions(files) {
    if (!files || files.length === 0) return;

    var pairs = {};
    for (var i = 0; i < files.length; i++) {
        var name = files[i].name;
        var base = name.replace(/\.(session|json)$/i, '');
        if (!pairs[base]) pairs[base] = {};
        if (name.endsWith('.session')) pairs[base].session = files[i];
        else if (name.endsWith('.json')) pairs[base].json = files[i];
    }

    var pairList = Object.keys(pairs).filter(function(k) { return pairs[k].session && pairs[k].json; });
    var unpaired = Object.keys(pairs).filter(function(k) { return !pairs[k].session || !pairs[k].json; });

    if (unpaired.length > 0) {
        showToast('Нет пары для: ' + unpaired.join(', ') + '. Нужен .session + .json с одинаковым именем', 'error');
    }

    if (pairList.length === 0) {
        showToast('Нет ни одной пары .session + .json', 'error');
        return;
    }

    var total = pairList.length;
    var done = 0, ok = 0, fail = 0;

    document.getElementById('upload-progress').style.display = 'block';
    document.getElementById('upload-status').textContent = 'Загрузка 0/' + total + ' аккаунтов...';
    document.getElementById('upload-bar').style.width = '0%';

    function uploadPair(idx) {
        if (idx >= pairList.length) {
            document.getElementById('upload-status').textContent = 'Готово: ' + ok + ' загружено, ' + fail + ' ошибок';
            showToast('Загружено ' + ok + '/' + total + ' аккаунтов', ok > 0 ? 'success' : 'error');
            refreshStatus();
            loadAccounts();
            return;
        }

        var base = pairList[idx];
        var pair = pairs[base];
        var fd = new FormData();
        fd.append('file', pair.session);
        fd.append('file', pair.json);

        fetch('/api/accounts/upload_session', {method: 'POST', body: fd})
        .then(function(r) { return r.json(); })
        .then(function(d) {
            if (d.ok) { ok++; } else { fail++; showToast('Ошибка ' + base + ': ' + (d.error || '?'), 'warning'); }
        })
        .catch(function() { fail++; })
        .finally(function() {
            done++;
            var pct = Math.round(done / total * 100);
            document.getElementById('upload-bar').style.width = pct + '%';
            document.getElementById('upload-status').textContent = 'Загрузка ' + done + '/' + total + '...';
            uploadPair(idx + 1);
        });
    }
    uploadPair(0);
}

function syncAndReload() {
    // Сначала синхронизируем (удаляем из списка те чьи файлы удалены)
    API.post('/api/accounts/sync').then(function(d) {
        if (d.removed > 0) {
            showToast('Удалено ' + d.removed + ' аккаунтов (файлы не найдены)', 'info');
        }
        // Потом сканируем папку на новые
        return API.post('/api/accounts/scan_folder');
    }).then(function(d) {
        if (d && d.ok) {
            if (d.added > 0) showToast(d.msg, 'success');
            else showToast('Синхронизировано', 'success');
        }
        loadAccounts();
    }).catch(function() { showToast('Ошибка синхронизации', 'error'); });
}

function openAccountsFolder() {
    // Открыть ту папку, чей фильтр сейчас активен
    var folderName = '';
    if (_accFilter && _accFilter !== 'all') {
        var f = _dynamicFilters.find(function(x) { return x.id === _accFilter; });
        if (f && f.folder) folderName = f.folder;
    }
    API.post('/api/accounts/open_folder', { folder: folderName }).then(function(d) {
        if (d.ok) showToast('Папка открыта: ' + (d.path || ''), 'info');
        else showToast(d.error || 'Ошибка', 'error');
    }).catch(function() {});
}

function scanFolder() {
    showToast('Сканирую аккаунты...', 'info');
    API.post('/api/accounts/sync').then(function() {
        return API.post('/api/accounts/scan_folder');
    }).then(function(d) {
        if (d.ok) {
            showToast(d.msg, d.added > 0 ? 'success' : 'info');
            if (d.errors && d.errors.length > 0) {
                d.errors.forEach(function(e) {
                    if (typeof addLog === 'function') addLog('Ошибка: ' + e, 'WARNING');
                });
            }
            loadAccounts();
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    }).catch(function() { showToast('Ошибка сканирования', 'error'); });
}

// ══════════════════════════════════════════════════════════════════
//  PROXY — прокси
// ══════════════════════════════════════════════════════════════════

function loadProxies() {
    API.get('/api/proxy/list').then(function(d) {
        var el = document.getElementById('proxy-list');
        if (!el) return;
        var proxies = d.proxies || [];
        if (proxies.length === 0) {
            el.innerHTML = '<div class="empty-state">' +
                '<i class="bi bi-shield-exclamation empty-state__icon" style="color:var(--warning);"></i>' +
                '<div class="empty-state__title">Прокси не настроены</div>' +
                '<div class="empty-state__text">Без прокси аккаунты ходят через ваш IP — Telegram может ассоциировать их между собой и заблокировать пачкой. Добавьте прокси чтобы развести аккаунты по разным IP.</div>' +
            '</div>';
            return;
        }
        var countLabel = document.getElementById('proxy-count-label');
        if (countLabel) {
            var alive = proxies.filter(function(p) { return p.alive === true; }).length;
            countLabel.textContent = alive + '/' + proxies.length + ' живых';
        }
        el.innerHTML = proxies.map(function(p, i) {
            var statusColor = p.alive === true ? 'var(--success)' : p.alive === false ? 'var(--danger)' : 'var(--text-muted)';
            var statusText = p.alive === true ? 'Живой' + (p.proto ? ' (' + p.proto + ')' : '') : p.alive === false ? 'Мёртвый' : 'Не проверен';
            var statusBadge = p.alive === true
                ? '<span class="badge-soft badge-soft--success"><span class="status-dot status-dot--running" style="margin-right:0;"></span>Живой' + (p.proto ? ' · ' + p.proto : '') + '</span>'
                : p.alive === false
                ? '<span class="badge-soft badge-soft--danger">Мёртвый</span>'
                : '<span class="badge-soft badge-soft--muted">Не проверен</span>';
            var errLine = p.error ? '<div style="font-size:0.65rem;color:var(--danger);margin-top:2px;opacity:0.8;">' + escHtml(p.error).substring(0, 80) + '</div>' : '';
            // Гео exit-IP — показываем реальную страну через которую идёт трафик.
            // Важно: клиенты часто не знают что их «русский» прокси на самом деле
            // выходит через польский дата-центр (proxyma.io и др. смешивают пулы).
            var geoLine = '';
            if (p.alive === true && p.country) {
                var flag = (p.cc && p.cc !== '??') ? ('<img src="https://flagcdn.com/16x12/' + p.cc.toLowerCase() + '.png" style="vertical-align:middle;margin-right:4px;" alt="' + p.cc + '">') : '';
                var where = p.city ? (p.city + ', ' + p.country) : p.country;
                var ip = p.exit_ip ? ('<span style="color:var(--text-muted);margin-left:6px;">' + p.exit_ip + '</span>') : '';
                geoLine = '<div style="font-size:0.7rem;margin-top:2px;">' + flag + escHtml(where) + ip + '</div>';
            }
            return '<div class="hover-row" style="padding:10px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;gap:10px;">' +
                '<div style="flex:1;min-width:0;">' +
                    '<div style="font-size:0.8rem;font-family:monospace;overflow:hidden;text-overflow:ellipsis;">' +
                        escHtml(p.addr) +
                    '</div>' +
                    geoLine +
                    errLine +
                '</div>' +
                '<div style="display:flex;align-items:center;gap:6px;flex-shrink:0;">' +
                    statusBadge +
                    '<button onclick="recheckProxy(' + i + ')" class="btn btn-outline btn-sm btn-icon" title="Перепроверить"><i class="bi bi-arrow-clockwise"></i></button>' +
                    '<button onclick="removeProxy(' + i + ')" class="btn btn-outline btn-sm btn-icon" style="color:var(--danger);" title="Удалить"><i class="bi bi-trash"></i></button>' +
                '</div>' +
            '</div>';
        }).join('');
    });
}

function addProxies() {
    var input = document.getElementById('proxy-input');
    if (!input) return;
    var text = input.value.trim();
    if (!text) { showToast('Введите прокси', 'warning'); return; }
    var defaultType = (document.getElementById('proxy-type-default') || {}).value || 'socks5';
    API.post('/api/proxy/add', {text: text, default_type: defaultType}).then(function(d) {
        if (d.ok) {
            showToast('Добавлено. Всего: ' + d.count + ' прокси', 'success');
            input.value = '';
            loadProxies();
        }
    }).catch(function() { showToast('Ошибка', 'error'); });
}

function checkAllProxies() {
    showToast('Проверяю все прокси (socks5/http/socks4)...', 'info');
    API.post('/api/proxy/check').then(function(d) {
        if (d.ok) {
            showToast('Проверено: ' + d.alive + '/' + d.total + ' живых', d.alive > 0 ? 'success' : 'warning');
            loadProxies();
        }
    }).catch(function() { showToast('Ошибка проверки', 'error'); });
}

function recheckProxy(idx) {
    showToast('Перепроверяю прокси #' + (idx+1) + '...', 'info');
    API.post('/api/proxy/recheck', {index: idx}).then(function(d) {
        if (d.ok) {
            var msg;
            if (d.alive) {
                msg = 'Прокси живой (' + d.proto + ')';
                if (d.country) msg += ' — ' + d.country + (d.exit_ip ? ' / ' + d.exit_ip : '');
            } else {
                msg = 'Прокси мёртвый: ' + (d.error || '').substring(0, 60);
            }
            showToast(msg, d.alive ? 'success' : 'warning');
            loadProxies();
        }
    }).catch(function() { showToast('Ошибка', 'error'); });
}

function removeProxy(idx) {
    API.post('/api/proxy/remove', {index: idx}).then(function(d) {
        if (d.ok) {
            showToast('Прокси удалён', 'success');
            loadProxies();
        }
    });
}

// ══════════════════════════════════════════════════════════════════
//  TEMPLATES — готовые тексты
// ══════════════════════════════════════════════════════════════════

var _currentTplCat = 'all';

function loadTemplates(cat) {
    _currentTplCat = cat || 'all';
    API.get('/api/templates?cat=' + _currentTplCat).then(function(d) {
        var el = document.getElementById('templates-list');
        if (!el) return;
        var tpls = d.templates || [];
        if (tpls.length === 0) {
            el.innerHTML = '<div class="text-muted text-sm" style="text-align:center;padding:16px;">Нет шаблонов в этой категории</div>';
            return;
        }
        el.innerHTML = tpls.map(function(t) {
            var catLabels = {work: 'Работа', sales: 'Продажи', crypto: 'Крипто', custom: 'Мой'};
            var catColors = {work: 'var(--success)', sales: 'var(--info)', crypto: 'var(--warning)', custom: 'var(--accent-light)'};
            var delBtn = t.id ?
                '<button onclick="deleteTemplate(\'' + t.id + '\',event)" style="background:none;border:none;color:var(--danger);cursor:pointer;font-size:0.75rem;padding:2px 4px;" title="Удалить"><i class="bi bi-x-lg"></i></button>' : '';
            return '<div class="card mb-2" style="cursor:pointer;" onclick="useTemplate(\'' + escHtml(t.text).replace(/'/g, "\\'").replace(/\n/g, "\\n") + '\')">' +
                '<div class="card-body" style="padding:12px;">' +
                    '<div class="d-flex align-center justify-between mb-2">' +
                        '<span style="font-weight:600;font-size:0.85rem;">' + escHtml(t.name) + '</span>' +
                        '<div style="display:flex;align-items:center;gap:6px;">' +
                            delBtn +
                            '<span style="font-size:0.65rem;color:' + (catColors[t.cat] || 'var(--text-muted)') + ';background:var(--surface-info-bg);padding:2px 6px;border-radius:4px;">' + (catLabels[t.cat] || t.cat) + '</span>' +
                        '</div>' +
                    '</div>' +
                    '<div style="font-size:0.78rem;color:var(--text-muted);white-space:pre-wrap;">' + escHtml(t.text) + '</div>' +
                '</div>' +
            '</div>';
        }).join('');
    });
}

function filterTemplates(cat) {
    loadTemplates(cat);
    document.querySelectorAll('#tab-templates .btn-outline').forEach(function(b) { b.classList.remove('active'); });
    event.target.classList.add('active');
}

function useTemplate(text) {
    var spamTpl = document.getElementById('spam-tpl');
    var dmTpl = document.getElementById('dm-tpl');
    if (spamTpl) spamTpl.value = text;
    if (dmTpl) dmTpl.value = text;
    showToast('Шаблон вставлен в рассылку и DM', 'success');
}

function deleteTemplate(id, e) {
    e.stopPropagation();
    // Удаляем без confirm — просто удаляем
    API.post('/api/templates/delete', {id: id}).then(function(d) {
        if (d.ok) loadTemplates(_currentTplCat);
    });
}

function createTemplate() {
    var form = document.getElementById('tpl-create-form');
    if (form) { form.style.display = form.style.display === 'none' ? 'block' : 'none'; return; }

    // Создаём форму инлайн
    var container = document.getElementById('tpl-create-area');
    if (!container) return;
    container.innerHTML =
        '<div id="tpl-create-form" class="card mt-2" style="border-color:var(--accent);">' +
            '<div class="card-body" style="padding:12px;">' +
                '<label class="form-label">Название</label>' +
                '<input class="form-control mb-2" id="tpl-new-name" placeholder="Мой шаблон">' +
                '<label class="form-label">Текст (спинтакс: {вариант1|вариант2})</label>' +
                '<textarea class="form-control mb-2" id="tpl-new-text" rows="3" placeholder="{Привет|Здравствуйте}! Есть предложение..."></textarea>' +
                '<div class="d-flex gap-2">' +
                    '<button class="btn btn-accent btn-sm" onclick="saveNewTemplate()"><i class="bi bi-check"></i> Сохранить</button>' +
                    '<button class="btn btn-outline btn-sm" onclick="document.getElementById(\'tpl-create-form\').style.display=\'none\'">Отмена</button>' +
                '</div>' +
            '</div>' +
        '</div>';
}

function saveNewTemplate() {
    var name = document.getElementById('tpl-new-name').value.trim();
    var text = document.getElementById('tpl-new-text').value.trim();
    if (!name || !text) { showToast('Заполните название и текст', 'warning'); return; }
    API.post('/api/templates/create', {name: name, text: text}).then(function(d) {
        if (d.ok) {
            showToast('Шаблон создан!', 'success');
            document.getElementById('tpl-create-form').style.display = 'none';
            loadTemplates('custom');
        }
    }).catch(function() { showToast('Ошибка', 'error'); });
}

// ══════════════════════════════════════════════════════════════════
//  PARSER — сбор юзернеймов
// ══════════════════════════════════════════════════════════════════

var _parserChatsData = [];

function loadParserChats(forceWake) {
    var sel = document.getElementById('parser-chat');
    sel.innerHTML = '<option value="">Загрузка чатов...</option>';
    renderAccountSelectors();
    var descEl = document.getElementById('parser-mode-desc');
    if (descEl && _parserModeDescs[_parserMode]) descEl.innerHTML = _parserModeDescs[_parserMode];
    // Авто-сон убран (1.4.55) — параметр wake больше не нужен, сервер всегда коннектит.
    API.get('/api/parser/chats').then(function(d) {
        var chats = d.chats || [];
        _parserChatsData = chats;
        if (chats.length === 0) {
            sel.innerHTML = '<option value="">Нет чатов — выберите аккаунт</option>';
            updateChatPreview();
            return;
        }
        sel.innerHTML = '<option value="">Выберите чат (' + chats.length + ' доступно)</option>';
        chats.forEach(function(c) {
            var icon = c.type === 'channel' ? '[канал]' : '[группа]';
            var members = c.members > 0 ? ' (' + c.members.toLocaleString() + ')' : '';
            var opt = document.createElement('option');
            opt.value = c.id;
            opt.textContent = icon + ' ' + c.name + members;
            sel.appendChild(opt);
        });
        updateChatPreview();
    }).catch(function() { sel.innerHTML = '<option value="">Ошибка загрузки</option>'; });
}

function filterParserChats() {
    var inp = document.getElementById('parser-chat-search');
    var sel = document.getElementById('parser-chat');
    if (!sel) return;
    var q = (inp ? inp.value : '').trim().toLowerCase();
    var visible = 0, total = 0;
    for (var i = 0; i < sel.options.length; i++) {
        var opt = sel.options[i];
        if (!opt.value) continue;
        total++;
        var match = !q || (opt.textContent || '').toLowerCase().indexOf(q) !== -1;
        opt.hidden = !match;
        opt.disabled = !match;
        if (match) visible++;
    }
    if (sel.options.length > 0 && !sel.options[0].value) {
        sel.options[0].textContent = q
            ? (visible ? 'Найдено: ' + visible + ' из ' + total : 'Ничего не найдено')
            : 'Выберите чат (' + total + ' доступно)';
    }
}

function updateChatPreview() {
    var preview = document.getElementById('parser-chat-preview');
    if (!preview) return;
    var sel = document.getElementById('parser-chat');
    var chatId = sel ? sel.value : '';
    if (!chatId) { preview.style.display = 'none'; return; }
    var chat = _parserChatsData.find(function(c) { return String(c.id) === String(chatId); });
    if (!chat) { preview.style.display = 'none'; return; }
    var icon = chat.type === 'channel' ? 'bi-megaphone' : 'bi-people';
    var typeLabel = chat.type === 'channel' ? 'Канал' : 'Группа';
    preview.style.display = 'block';
    preview.innerHTML = '<div class="chat-info">' +
        '<i class="bi ' + icon + '"></i>' +
        '<span>' + escHtml(chat.name) + '</span>' +
        '<span style="font-size:0.7rem;color:var(--text-muted);background:var(--surface-info-bg);padding:2px 6px;border-radius:4px;">' + typeLabel + '</span>' +
        '<span class="chat-members"><i class="bi bi-people"></i> ' + (chat.members || 0).toLocaleString() + '</span>' +
    '</div>';
}

var _parserPollTimer = null;
var _parserMode = 'members';
var _parserSelectedChats = [];  // [{id, name}]
var _parserLinksMode = false;
var _parserAllChats = false;

function toggleParserInputMode() {
    _parserLinksMode = !_parserLinksMode;
    var selMode = document.getElementById('parser-chat-select-mode');
    var linksMode = document.getElementById('parser-links-mode');
    var btn = document.getElementById('btn-parser-input-mode');
    if (_parserLinksMode) {
        if (selMode) selMode.style.display = 'none';
        if (linksMode) linksMode.style.display = 'block';
        if (btn) btn.innerHTML = '<i class="bi bi-list-ul"></i> Из списка';
    } else {
        if (selMode) selMode.style.display = 'block';
        if (linksMode) linksMode.style.display = 'none';
        if (btn) btn.innerHTML = '<i class="bi bi-link-45deg"></i> Ввести ссылки';
    }
}

function toggleParserAllChats() {
    _parserAllChats = !_parserAllChats;
    var banner = document.getElementById('parser-all-chats-banner');
    if (banner) banner.style.display = _parserAllChats ? 'block' : 'none';
}

// Один клик — сканируем все чаты аккаунта (как в старом link_collector боте).
// Для режимов keyword/search/user_chats — нет смысла, форсим режим «Ссылки».
// Для links/members/writers — оставляем текущий режим + включаем all_chats и жмём Старт.
function scanAllChatsOneClick() {
    if (_parserMode === 'keyword' || _parserMode === 'search' || _parserMode === 'user_chats') {
        setParserMode('links');
    }
    _parserAllChats = true;
    var banner = document.getElementById('parser-all-chats-banner');
    if (banner) banner.style.display = 'block';
    var lbl = _parserMode === 'members' ? 'участников' :
              _parserMode === 'writers' ? 'писавших' : 'ссылки';
    showToast('Сканирую все чаты аккаунта → собираю ' + lbl + '...', 'info');
    setTimeout(function() { startParser(); }, 100);
}

function addParserChat() {
    var sel = document.getElementById('parser-chat');
    // Если чат выбран в списке — добавляем его
    if (sel && sel.value) {
        var id = sel.value;
        var name = sel.options[sel.selectedIndex].text;
        if (_parserSelectedChats.some(function(c) { return c.id === id; })) {
            showToast('Этот чат уже добавлен', 'warning');
            return;
        }
        _parserSelectedChats.push({id: id, name: name});
        _renderParserSelectedChats();
        showToast('Чат добавлен: ' + name.substring(0, 40), 'success');
        return;
    }
    // Нет выбранного — просим ссылку или @username вручную
    var input = prompt('Введите ссылку или @username чата:\n(например @telegram или https://t.me/telegram)');
    if (!input) return;
    input = input.trim();
    if (!input) return;
    // Нормализация
    var username = input.replace(/^https?:\/\/(t\.me|telegram\.me)\//i, '').replace(/^@/, '').split('?')[0].split('/')[0];
    if (!username) {
        showToast('Не удалось распознать ссылку', 'error');
        return;
    }
    if (_parserSelectedChats.some(function(c) { return c.id === username || c.name === '@' + username; })) {
        showToast('Этот чат уже добавлен', 'warning');
        return;
    }
    _parserSelectedChats.push({id: username, name: '@' + username});
    _renderParserSelectedChats();
    showToast('Чат добавлен: @' + username, 'success');
}

function removeParserChat(id) {
    _parserSelectedChats = _parserSelectedChats.filter(function(c) { return c.id !== id; });
    _renderParserSelectedChats();
}

function _renderParserSelectedChats() {
    var el = document.getElementById('parser-selected-chats');
    if (!el) return;
    if (!_parserSelectedChats.length) { el.innerHTML = ''; return; }
    el.innerHTML = _parserSelectedChats.map(function(c) {
        return '<span style="background:var(--surface-info-bg);border:1px solid var(--border-accent);border-radius:6px;padding:3px 8px;font-size:0.72rem;display:inline-flex;align-items:center;gap:4px;">' +
            escHtml(c.name.substring(0, 30)) +
            '<span onclick="removeParserChat(\'' + c.id + '\')" style="cursor:pointer;color:var(--text-muted);font-size:0.8rem;">&times;</span>' +
        '</span>';
    }).join('');
}

var _parserModeDescs = {
    members: '<b>Участники</b> — @юзернеймы из списка участников чата. Фильтры: активность, пропуск админов, спам-детекция. Расширенный алфавит (лат+кир).',
    writers: '<b>Писавшие</b> — @юзернеймы из сообщений. Для каналов автоматически собирает комментаторов + @mentions из постов.',
    links: '<b>Ссылки</b> — t.me из сообщений, entities, кнопок. Unwrap коротких URL (bit.ly, telegra.ph). Каноникализация и дедупликация.',
    keyword: '<b>По словам</b> — поиск юзернеймов и ссылок по ключевым словам во ВСЕХ чатах аккаунта.',
    search: '<b>Глоб. поиск</b> — найти публичные чаты/каналы по ключевому слову через Telegram API + TGStat. Транслитерация RU→LAT, расширение запросов.',
    user_chats: '<b>Чаты юзера</b> — ищет чаты/каналы, где состоит указанный @юзернейм. Полный список чатов юзера Telegram API не отдаёт никому — доступны только те, где есть пересечение с вашими аккаунтами. Чем больше аккаунтов задействовано, тем шире охват.',
};

// Быстрые пресеты активности для members/writers
function applyActivityPreset(kind, hours) {
    var filterEl = document.getElementById('parser-filter');
    var hoursEl = document.getElementById('parser-hours');
    if (kind === 'online') {
        if (filterEl) filterEl.value = 'online';
    } else if (kind === 'all') {
        if (filterEl) filterEl.value = 'all';
    } else if (kind === 'hours') {
        if (filterEl) filterEl.value = 'hours';
        if (hoursEl) hoursEl.value = hours;
    }
    // Подсвечиваем активную кнопку
    var btns = document.querySelectorAll('#parser-activity-presets button');
    btns.forEach(function(b) { b.classList.remove('btn-accent'); b.classList.add('btn-outline'); });
    if (event && event.currentTarget) {
        event.currentTarget.classList.remove('btn-outline');
        event.currentTarget.classList.add('btn-accent');
    }
}

// Селект «Период сканирования» в режиме «Писавшие» — самодостаточен,
// значение читается в startParser() напрямую из #parser-period-preset.
function applyPeriodPreset(days) {
    // noop: оставлен для совместимости со старыми HTML-атрибутами onchange
}

// Селект «Период сканирования» в режиме «Ссылки» — аналогично, читается напрямую.
function applyLinksPeriodPreset(days) {
    // noop
}

// Собирает актуальное значение date_limit_days в зависимости от активного режима парсинга.
// В writers — из #parser-period-preset, в links — из #parser-links-period, иначе 0.
function _currentDateLimitDays(mode) {
    var el = null;
    if (mode === 'writers') el = document.getElementById('parser-period-preset');
    else if (mode === 'links') el = document.getElementById('parser-links-period');
    if (!el) return 0;
    var v = parseInt(el.value);
    return isNaN(v) ? 0 : v;
}

function setParserMode(mode) {
    _parserMode = mode;
    ['members','writers','links','keyword','search','user_chats'].forEach(function(m) {
        var btn = document.getElementById('pm-' + m);
        if (btn) btn.className = 'parser-mode-btn' + (m === mode ? ' active' : '');
    });
    document.getElementById('parser-opts-users').style.display = (mode === 'members' || mode === 'writers') ? 'block' : 'none';
    // min_messages + период сканирования — только для режима «Писавшие».
    // В «Участниках» они не нужны (участники берутся из API-списка, не из сообщений).
    var writersExtra = document.getElementById('parser-writers-extra');
    if (writersExtra) writersExtra.style.display = (mode === 'writers') ? 'grid' : 'none';
    document.getElementById('parser-opts-links').style.display = mode === 'links' ? 'block' : 'none';
    document.getElementById('parser-opts-keyword').style.display = mode === 'keyword' ? 'block' : 'none';
    var searchOpts = document.getElementById('parser-opts-search');
    if (searchOpts) searchOpts.style.display = mode === 'search' ? 'block' : 'none';
    var presets = document.getElementById('parser-presets');
    if (presets) presets.style.display = (mode === 'links') ? 'block' : 'none';
    var chatRow = document.getElementById('parser-chat-row');
    if (chatRow) chatRow.style.display = (mode === 'keyword' || mode === 'search' || mode === 'user_chats') ? 'none' : 'block';
    // «Сканировать все чаты» — осмысленно только в режиме «Ссылки»:
    // там собираются t.me URL и логично пройти по всем группам сразу.
    // В «Участники»/«Писавшие» прогон по всем чатам = миллионы записей и FloodWait, поэтому скрываем.
    var scanAllWrap = document.getElementById('parser-scan-all-wrap');
    if (scanAllWrap) scanAllWrap.style.display = (mode === 'links') ? 'block' : 'none';
    var userRow = document.getElementById('parser-user-row');
    if (userRow) userRow.style.display = (mode === 'user_chats') ? 'block' : 'none';
    var descEl = document.getElementById('parser-mode-desc');
    if (descEl) {
        // Для user_chats у нас уже есть подробная инфо-рамка рядом с полем ввода
        // (parser-user-row). Дублировать описание сверху — мусор, скрываем.
        if (mode === 'user_chats') {
            descEl.style.display = 'none';
            descEl.innerHTML = '';
        } else {
            descEl.style.display = '';
            descEl.innerHTML = _parserModeDescs[mode] || '';
        }
    }
}

function startParser() {
    // Собираем чаты: из мульти-выбора, из ссылок, или из select
    var chatIds = [];
    var chatLinks = '';
    var allChats = !!_parserAllChats && (_parserMode !== 'keyword' && _parserMode !== 'search' && _parserMode !== 'user_chats');
    if (allChats) {
        // Режим «все чаты» — сервер сам развернёт через iter_dialogs
    } else if (_parserLinksMode) {
        chatLinks = (document.getElementById('parser-chat-links') || {}).value || '';
        if (_parserMode !== 'keyword' && _parserMode !== 'search' && !chatLinks.trim()) {
            showToast('Вставьте ссылки на чаты', 'warning'); return;
        }
    } else if (_parserSelectedChats.length > 0) {
        chatIds = _parserSelectedChats.map(function(c) { return c.id; });
    } else {
        var chatId = document.getElementById('parser-chat').value;
        if (_parserMode !== 'keyword' && _parserMode !== 'search' && !chatId) {
            showToast('Выберите чат', 'warning'); return;
        }
        if (chatId) chatIds = [chatId];
    }

    if (_parserMode === 'keyword') {
        var kw = document.getElementById('parser-keywords').value.trim();
        if (!kw) { showToast('Введите ключевые слова', 'warning'); return; }
    }
    if (_parserMode === 'search') {
        var sq = (document.getElementById('parser-search-query') || {}).value || '';
        if (!sq.trim()) { showToast('Введите поисковый запрос', 'warning'); return; }
    }
    if (_parserMode === 'user_chats') {
        var tu = (document.getElementById('parser-target-user') || {}).value || '';
        if (!tu.trim()) { showToast('Введите @юзернейм или ID', 'warning'); return; }
    }

    var data = {
        chat_id: chatIds.length === 1 ? chatIds[0] : '',
        chat_ids: chatIds,
        chat_links: chatLinks,
        all_chats: allChats,
        mode: _parserMode,
        filter: document.getElementById('parser-filter').value,
        hours: parseInt(document.getElementById('parser-hours').value) || 24,
        limit: parseInt(document.getElementById('parser-limit').value) || 5000,
        link_type: document.getElementById('parser-link-type').value,
        keywords: _parserMode === 'keyword' ? document.getElementById('parser-keywords').value : '',
        kw_scope: document.getElementById('parser-kw-scope').value,
        kw_collect: document.getElementById('parser-kw-collect').value,
        // New params
        skip_admins: (document.getElementById('parser-skip-admins') || {}).checked || false,
        strict_activity: (document.getElementById('parser-strict') || {}).checked || false,
        date_limit_days: _currentDateLimitDays(_parserMode),
        min_messages: parseInt((document.getElementById('parser-min-messages') || {}).value) || 1,
        unwrap_urls: (document.getElementById('parser-unwrap') || {}).checked || false,
        search_query: (document.getElementById('parser-search-query') || {}).value || '',
        search_translit: (document.getElementById('parser-translit') || {}).checked !== false,
        search_source: (document.getElementById('parser-search-source') || {}).value || 'telegram',
        tgstat_token: (document.getElementById('parser-tgstat-token') || {}).value || '',
        min_participants: parseInt((document.getElementById('parser-min-members') || {}).value) || 0,
        expand_mode: (document.getElementById('parser-expand') || {}).value || 'full',
        // user_chats mode
        target_user: (document.getElementById('parser-target-user') || {}).value || '',
        use_all_accs: (document.getElementById('parser-user-all-accs') || {}).checked || false,
        // Расширенные фильтры
        save_mode: (document.getElementById('parser-save-mode') || {}).value || 'usernames',
        roles_filter: (document.getElementById('parser-roles') || {}).value || 'users',
        dm_access: (document.getElementById('parser-dm-access') || {}).value || 'all',
        online_min_hours: parseInt((document.getElementById('parser-online-min') || {}).value) || 0,
        online_max_hours: parseInt((document.getElementById('parser-online-max') || {}).value) || 99999,
        only_avatar: (document.getElementById('parser-only-avatar') || {}).checked || false,
        only_premium: (document.getElementById('parser-only-premium') || {}).checked || false,
        include_poll_voters: (document.getElementById('parser-include-poll-voters') || {}).checked || false,
    };

    document.getElementById('btn-parser').disabled = true;
    var _stopBtn = document.getElementById('btn-parser-stop');
    if (_stopBtn) { _stopBtn.style.display = ''; _stopBtn.disabled = false; }
    document.getElementById('parser-progress').style.display = 'block';
    document.getElementById('parser-result').style.display = 'none';
    document.getElementById('parser-bar').style.width = '0%';
    document.getElementById('parser-status-text').textContent = 'Запуск парсинга...';

    API.post('/api/parser/start', data).then(function(d) {
        if (d.ok) {
            showToast('Парсинг запущен!', 'success');
            _parserPollTimer = setInterval(pollParserStatus, 1500);
        } else {
            showToast(d.error || 'Ошибка', 'error');
            document.getElementById('btn-parser').disabled = false;
            if (_stopBtn) _stopBtn.style.display = 'none';
        }
        // Сбрасываем флаг all_chats после запуска — следующий парсинг не должен
        // случайно зацепить все чаты если юзер уже выбрал конкретные.
        _parserAllChats = false;
        var banner = document.getElementById('parser-all-chats-banner');
        if (banner) banner.style.display = 'none';
    }).catch(function(e) {
        showToast(e.error || 'Ошибка', 'error');
        document.getElementById('btn-parser').disabled = false;
        if (_stopBtn) _stopBtn.style.display = 'none';
        _parserAllChats = false;
        var banner2 = document.getElementById('parser-all-chats-banner');
        if (banner2) banner2.style.display = 'none';
    });
}

function stopParser() {
    showConfirmDialog('Остановить парсинг?', 'Уже собранные данные сохранятся.', function() {
        var btn = document.getElementById('btn-parser-stop');
        if (btn) {
            btn.disabled = true;
            btn.innerHTML = '<i class="bi bi-hourglass-split"></i> Останавливаю...';
        }
        API.post('/api/parser/stop', {}).then(function(d) {
            if (d.ok) {
                showToast('Запрос на остановку отправлен', 'info');
            } else {
                showToast(d.error || 'Не удалось остановить', 'error');
                if (btn) { btn.disabled = false; btn.innerHTML = '<i class="bi bi-stop-fill"></i> Остановить'; }
            }
        }).catch(function(e) {
            showToast('Ошибка: ' + e, 'error');
            if (btn) { btn.disabled = false; btn.innerHTML = '<i class="bi bi-stop-fill"></i> Остановить'; }
        });
    });
}

function forceStopParser() {
    showConfirmDialog('Принудительно сбросить парсинг?', 'Это пометит задачу как остановленную и позволит запустить новую. Фоновый запрос всё равно будет доживать в памяти.', function() {
        API.post('/api/parser/stop', {force: true}).then(function(d) {
            if (d.ok) {
                showToast('Парсинг сброшен', 'info');
                clearInterval(_parserPollTimer);
                var btn = document.getElementById('btn-parser'); if (btn) btn.disabled = false;
                var sb = document.getElementById('btn-parser-stop'); if (sb) sb.style.display = 'none';
                var hint = document.getElementById('parser-stuck-hint'); if (hint) hint.remove();
                var status = document.getElementById('parser-status-text');
                if (status) status.textContent = 'Сброшено';
            } else {
                showToast(d.error || 'Ошибка', 'error');
            }
        });
    });
}

function pollParserStatus() {
    API.get('/api/parser/status').then(function(d) {
        var pct = d.count > 0 ? Math.min(100, Math.round(d.count / 50 * 100)) : (d.scanned > 0 ? Math.min(90, Math.round(d.scanned / 100)) : 5);
        var bar = document.getElementById('parser-bar');
        bar.style.width = pct + '%';
        bar.classList.add('is-running');
        var stext = 'Найдено: ' + d.progress + ' | Просканировано: ' + d.scanned + ' | ' + d.elapsed + 'с';
        if (d.status_text) stext = d.status_text + ' — ' + stext;
        document.getElementById('parser-status-text').textContent = stext;
        // Если за 2 минуты нет прогресса — предлагаем force-reset
        if (d.status === 'running' && d.elapsed > 120 && d.progress === 0 && d.scanned === 0) {
            var hint = document.getElementById('parser-stuck-hint');
            if (!hint) {
                hint = document.createElement('div');
                hint.id = 'parser-stuck-hint';
                hint.style.cssText = 'margin-top:8px;padding:8px 12px;background:var(--surface-danger-bg);border:1px solid var(--surface-danger-border);border-radius:6px;color:var(--danger);font-size:0.78rem;';
                hint.innerHTML = 'Парсинг не двигается уже ' + d.elapsed + 'с. <a href="#" onclick="forceStopParser();return false;" style="color:#fff;text-decoration:underline;">Принудительно сбросить</a>';
                var prog = document.getElementById('parser-progress');
                if (prog) prog.appendChild(hint);
            } else {
                hint.innerHTML = 'Парсинг не двигается уже ' + d.elapsed + 'с. <a href="#" onclick="forceStopParser();return false;" style="color:#fff;text-decoration:underline;">Принудительно сбросить</a>';
            }
        }

        if (d.status === 'finished' || d.status === 'error' || d.status === 'stopped') {
            clearInterval(_parserPollTimer);
            document.getElementById('btn-parser').disabled = false;
            var _sb = document.getElementById('btn-parser-stop');
            if (_sb) {
                _sb.style.display = 'none';
                _sb.disabled = false;
                _sb.innerHTML = '<i class="bi bi-stop-fill"></i> Остановить';
            }

            if (d.status === 'finished') {
                var _pb = document.getElementById('parser-bar');
                _pb.style.width = '100%';
                _pb.classList.remove('is-running');
                document.getElementById('parser-status-text').textContent = 'Готово! ' + d.count + ' юзернеймов за ' + d.elapsed + 'с';
                showToast('Парсинг завершён: ' + d.count + ' юзернеймов', 'success');
                playSound('success');
                loadParserResult();
            } else if (d.status === 'stopped') {
                document.getElementById('parser-status-text').textContent = 'Остановлено пользователем. Собрано: ' + d.count + ' юзернеймов';
                showToast('Парсинг остановлен, собрано: ' + d.count, 'info');
                if (d.count > 0) loadParserResult();
            } else {
                showToast('Ошибка парсинга: ' + (d.error || ''), 'error');
                playSound('error');
                document.getElementById('parser-status-text').textContent = 'Ошибка: ' + (d.error || '');
            }
        }
    });
}

function loadParserResult() {
    API.get('/api/parser/result').then(function(d) {
        var result = d.usernames || [];
        document.getElementById('parser-result').style.display = 'block';

        // Режим user_chats — результат это список чатов (dicts с link/title)
        var isUserChats = result.length > 0 && typeof result[0] === 'object' && result[0] !== null && ('link' in result[0] || 'chat_id' in result[0]);
        if (isUserChats) {
            document.getElementById('parser-count').textContent = result.length + ' чатов';
            // Текстовый вид для preview: title — link
            var lines = result.map(function(c) {
                var link = c.link || (c.username ? 'https://t.me/' + c.username : '(приватный, id=' + c.chat_id + ')');
                var typeIcon = c.type === 'channel' ? 'bi-megaphone' : (c.type === 'group' ? 'bi-people-fill' : 'bi-chat-dots-fill');
                var typeLabel = '<i class="bi ' + typeIcon + '" style="color:var(--text-muted);"></i>';
                return typeLabel + ' ' + (c.title || c.username || c.chat_id) + ' — ' + link + (c.members ? ' (' + c.members + ')' : '');
            });
            document.getElementById('parser-preview').value = lines.slice(0, 300).join('\n') + (lines.length > 300 ? '\n...(ещё ' + (lines.length - 300) + ')' : '');
        } else {
            document.getElementById('parser-count').textContent = result.length + ' юзернеймов';
            document.getElementById('parser-preview').value = result.slice(0, 200).join('\n') + (result.length > 200 ? '\n...(ещё ' + (result.length - 200) + ')' : '');
        }

        // Stats
        API.get('/api/parser/status').then(function(s) {
            var stats = s.stats || {};
            var parts = [];
            if (stats.target_user) parts.push('Юзер: ' + (stats.target_username ? '@' + stats.target_username : stats.target_user) + (stats.target_name ? ' (' + stats.target_name.trim() + ')' : ''));
            if (stats.accounts_used) parts.push('Аккаунтов: ' + stats.accounts_used);
            if (stats.scanned) parts.push('Просканировано: ' + stats.scanned);
            if (stats.scanned_msgs) parts.push('Сообщений: ' + stats.scanned_msgs);
            if (stats.spam_skipped) parts.push('Спам-блок: ' + stats.spam_skipped);
            if (stats.no_username) parts.push('Без юзернейма: ' + stats.no_username);
            if (stats.filtered) parts.push('Отфильтровано: ' + stats.filtered);
            // Счётчик приватных/недоступных чатов (не ошибка — нормально для мульти-парсинга)
            if (stats.skipped_private) parts.push('<i class="bi bi-lock-fill"></i> Приватных/недоступных: ' + stats.skipped_private);
            // Keyword режим — диагностика чтоб юзер видел куда делись чаты
            if (stats.dialogs_total !== undefined && stats.keywords !== undefined) {
                parts.push('Диалогов: ' + stats.dialogs_total);
                parts.push('Подходят: ' + (stats.dialogs_after_scope || 0));
                if (stats.chats_searched !== undefined) parts.push('Обработано: ' + stats.chats_searched);
                if (stats.messages_found !== undefined) parts.push('Найдено сообщений: ' + stats.messages_found);
            }
            document.getElementById('parser-stats').textContent = parts.join(' | ');

            // Подсказка если Telegram ограничивает (выводим отдельной строкой под статой)
            var hintEl = document.getElementById('parser-api-hint');
            if (stats.hint) {
                if (!hintEl) {
                    hintEl = document.createElement('div');
                    hintEl.id = 'parser-api-hint';
                    hintEl.style.cssText = 'margin-top:8px;padding:10px;background:rgba(251,191,36,.1);border:1px solid rgba(251,191,36,.35);border-radius:8px;color:#fbbf24;font-size:13px;line-height:1.45;';
                    var statsEl = document.getElementById('parser-stats');
                    if (statsEl && statsEl.parentNode) statsEl.parentNode.insertBefore(hintEl, statsEl.nextSibling);
                }
                hintEl.textContent = stats.hint;
            } else if (hintEl) {
                hintEl.remove();
            }
        });
    });
}

function exportParser(fmt) {
    fmt = fmt || 'txt';
    showToast('Сохраняю ' + fmt + '...', 'info');
    API.get('/api/parser/export?fmt=' + fmt).then(function(d) {
        if (d && d.ok) {
            var name = (d.path || '').split(/[\\/]/).pop();
            showToast('Сохранено: ' + name + ' (' + d.count + ')', 'success');
            if (typeof addLog === 'function') addLog('Файл сохранён: ' + d.path, 'SUCCESS');
            // Предложим открыть папку
            setTimeout(function(){
                if (confirm('Открыть папку «Экспорт»?\n\n' + (d.folder || ''))) {
                    API.post('/api/parser/open_export_folder', {}).catch(function(){});
                }
            }, 300);
        } else {
            showToast((d && d.error) || 'Ошибка экспорта', 'error');
        }
    }).catch(function(e) {
        var msg = (e && (e.error || e.message)) ? (e.error || e.message) : 'Ошибка';
        showToast('Ошибка экспорта: ' + msg, 'error');
    });
}

function openExportFolder() {
    API.post('/api/parser/open_export_folder', {}).then(function(d) {
        if (!d.ok) showToast(d.error || 'Не удалось открыть папку', 'error');
    });
}

function copyParserPreview() {
    // Берём полный результат через /api/parser/result чтобы не ограничивать превью
    API.get('/api/parser/result').then(function(d) {
        var arr = d && (d.usernames || d.result || []);
        if (!arr.length) {
            var preview = (document.getElementById('parser-preview') || {}).value || '';
            copyToClipboard(preview, 'Скопировано из превью');
            return;
        }
        var lines = arr.map(function(x) {
            if (typeof x === 'string') return x;
            if (x.link) return x.link;
            if (x.username) return '@' + x.username;
            return String(x);
        });
        copyToClipboard(lines.join('\n'), 'Результат парсера скопирован');
    }).catch(function() {
        var preview = (document.getElementById('parser-preview') || {}).value || '';
        copyToClipboard(preview, 'Скопировано');
    });
}

// Категоризированный экспорт — раскладывает результат по подпапке
// Парсинг_<timestamp>/ с отдельными .txt на каждую категорию.
function exportParserByFolders() {
    showToast('Раскладываю по категориям...', 'info');
    API.get('/api/parser/export_folders').then(function(d) {
        if (d && d.ok) {
            var parts = [];
            var sum = d.summary || {};
            for (var k in sum) parts.push(k + ': ' + sum[k]);
            var msg = 'Готово. ' + (parts.join('  •  ') || ('Всего: ' + d.total));
            showToast(msg, 'success');
            if (typeof addLog === 'function') addLog('Экспорт: ' + (d.folder || '') + '  (' + parts.join(', ') + ')', 'SUCCESS');
        } else {
            showToast((d && d.error) || 'Ошибка', 'error');
        }
    }).catch(function(e) {
        showToast((e && e.error) || 'Ошибка', 'error');
    });
}

function applyParserPreset(preset) {
    if (preset === 'basic') {
        var el;
        el = document.getElementById('parser-links-period'); if (el) el.value = '7';
        el = document.getElementById('parser-link-type'); if (el) el.value = 'all';
        el = document.getElementById('parser-unwrap'); if (el) el.checked = false;
        el = document.getElementById('parser-limit'); if (el) el.value = 3000;
        showToast('Базовый пресет: 7 дней, без URL unwrap', 'info');
    } else if (preset === 'full') {
        var el;
        el = document.getElementById('parser-links-period'); if (el) el.value = '0';
        el = document.getElementById('parser-link-type'); if (el) el.value = 'all';
        el = document.getElementById('parser-unwrap'); if (el) el.checked = true;
        el = document.getElementById('parser-limit'); if (el) el.value = 10000;
        showToast('Полный пресет: все дни, unwrap вкл', 'info');
    } else if (preset === 'invites') {
        var el;
        el = document.getElementById('parser-links-period'); if (el) el.value = '0';
        el = document.getElementById('parser-link-type'); if (el) el.value = 'invites';
        el = document.getElementById('parser-unwrap'); if (el) el.checked = true;
        el = document.getElementById('parser-limit'); if (el) el.value = 5000;
        showToast('Пресет инвайтов: только invite-ссылки', 'info');
    }
}

function useParserForDm() {
    API.post('/api/parser/use_for_dm').then(function(d) {
        if (d.ok) {
            showToast('Добавлено ' + d.added + ' в DM рассылку (всего: ' + d.total + ')', 'success');
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    });
}

function useParserForJoin() {
    API.post('/api/parser/use_for_join').then(function(d) {
        if (d.ok) {
            showToast('Добавлено ' + d.added + ' ссылок в подписку (всего: ' + d.total + ')', 'success');
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    });
}

// ══════════════════════════════════════════════════════════════════
//  ANALYTICS — аналитика
// ══════════════════════════════════════════════════════════════════

var _chart = null;

function loadAnalytics() {
    API.get('/api/analytics').then(function(d) {
        if (!d.ok) return;
        // Update stats
        var el;
        el = document.getElementById('stat-sent'); if (el) el.textContent = d.totals.spam || 0;
        el = document.getElementById('stat-joined'); if (el) el.textContent = d.totals.joins || 0;
        el = document.getElementById('stat-blocked'); if (el) el.textContent = d.totals.blocked || 0;
        el = document.getElementById('stat-errors'); if (el) el.textContent = d.totals.errors || 0;

        // Chart
        if (typeof Chart === 'undefined') return;
        var canvas = document.getElementById('analytics-chart');
        if (!canvas) return;

        var days = d.days || [];
        var labels = days.map(function(_, i) { return (7 - i) + ' дней назад'; });
        labels[labels.length - 1] = 'Сегодня';
        var spamData = days.map(function(day) { return day.spam; });
        var joinData = days.map(function(day) { return day.joins; });

        if (_chart) _chart.destroy();
        _chart = new Chart(canvas.getContext('2d'), {
            type: 'bar',
            data: {
                labels: labels,
                datasets: [
                    { label: 'Отправлено', data: spamData, backgroundColor: '#3b82f6', borderRadius: 4 },
                    { label: 'Вступлений', data: joinData, backgroundColor: '#22c55e', borderRadius: 4 },
                ],
            },
            options: {
                responsive: true,
                plugins: { legend: { labels: { color: '#94a3b8', font: { size: 11 } } } },
                scales: {
                    x: { ticks: { color: '#64748b', font: { size: 10 } }, grid: { color: '#1e2433' } },
                    y: { ticks: { color: '#64748b', font: { size: 10 } }, grid: { color: '#1e2433' }, beginAtZero: true },
                },
            },
        });
    });
}

function exportExcel() {
    window.location.href = '/api/analytics/export';
    showToast('Скачиваю Excel отчёт...', 'info');
}

// ══════════════════════════════════════════════════════════════════
//  SETTINGS — настройки
// ══════════════════════════════════════════════════════════════════

// ── Прогрев: подсказка при выборе режима генерации ─────────────────────
function onWarmupGenModeChange() {
    var sel = document.getElementById('warmup-gen-mode');
    var hint = document.getElementById('warmup-gen-mode-hint');
    if (!sel || !hint) return;
    var mode = sel.value;
    if (mode === 'ai') {
        // Проверяем что AI настроен
        fetch('/api/ai/get').then(function(r){return r.json()}).then(function(d) {
            var enabled = d && d.status && d.status.enabled;
            if (enabled) {
                hint.innerHTML = '<span style="color:var(--success);"><i class="bi bi-check-circle-fill"></i> AI настроен (' + (d.status.model || '') + '). Прогрев будет писать через нейросеть.</span>';
            } else {
                hint.innerHTML = '<span style="color:var(--warning);"><i class="bi bi-exclamation-triangle-fill"></i> AI не настроен. <a href="javascript:showTab(\'settings\')" style="color:var(--accent-light);">Настройки → AI</a> — введи API ключ. Без ключа прогрев упадёт на шаблоны автоматически.</span>';
            }
        });
    } else {
        hint.innerHTML = 'Прогрев пишет из готового набора фраз. AI не используется, $ не тратятся.';
    }
}

// ── Нейрокомменты ──────────────────────────────────────────────────────
var _ncTimer = null;

function ncStart() {
    var channels = (document.getElementById('nc-channels') || {}).value || '';
    if (!channels.trim()) { showToast('Укажи каналы', 'warning'); return; }
    // Собираем выбранных аккаунтов из selector (multi)
    // Собираем выбранных: режим multi → _accSelectorPicked, режим one → _cachedActive,
    // режим all → все живые из _cachedAccounts.
    var phones = [];
    var selId = 'acc-selector-neuro-comments';
    var mode = (window._accSelectorMode || {})[selId] || 'one';
    if (mode === 'multi' && (window._accSelectorPicked || {})[selId]) {
        phones = (window._accSelectorPicked[selId] || []).slice();
    } else if (mode === 'all') {
        phones = (_cachedAccounts || [])
            .filter(function(a){ return !a.dead && !a.frozen; })
            .map(function(a){ return a.phone; });
    } else if (_cachedActive) {
        phones = [_cachedActive];
    }
    if (!phones.length) { showToast('Выбери хотя бы 1 аккаунт', 'warning'); return; }
    var payload = {
        channels: channels,
        phones: phones,
        cta: (document.getElementById('nc-cta') || {}).value || '',
        custom_prompt: (document.getElementById('nc-custom-prompt') || {}).value || '',
        poll_interval_min: +((document.getElementById('nc-poll-interval') || {}).value || 5),
        recency_min: +((document.getElementById('nc-recency') || {}).value || 60),
        delay_between_acc_min: +((document.getElementById('nc-delay-min') || {}).value || 2),
        delay_between_acc_max: +((document.getElementById('nc-delay-max') || {}).value || 15),
        max_comments_per_post: +((document.getElementById('nc-per-post') || {}).value || 1),
        max_per_acc_per_hour: +((document.getElementById('nc-per-hour') || {}).value || 5),
        max_per_acc_per_day: +((document.getElementById('nc-per-day') || {}).value || 30),
    };
    fetch('/api/neuro_comments/start', {
        method: 'POST', headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(payload)
    }).then(function(r){return r.json()}).then(function(d) {
        if (d.ok) {
            showToast('Нейрокомменты запущены: ' + d.channels + ' каналов, ' + d.accounts + ' акков', 'success');
            document.getElementById('btn-nc-start').style.display = 'none';
            document.getElementById('btn-nc-stop').style.display = '';
            document.getElementById('nc-counters').style.display = '';
            document.getElementById('nc-log').style.display = '';
            if (_ncTimer) clearInterval(_ncTimer);
            _ncTimer = setInterval(ncPoll, 4000);
            ncPoll();
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    });
}

function ncStop() {
    fetch('/api/neuro_comments/stop', {method: 'POST'}).then(function(r){return r.json()}).then(function() {
        showToast('Останавливаю...', 'info');
    });
}

function ncPoll() {
    fetch('/api/neuro_comments/status').then(function(r){return r.json()}).then(function(d) {
        var lbl = document.getElementById('nc-status-label');
        if (lbl) {
            lbl.textContent = (d.status === 'running') ? '● Работает' :
                              (d.status === 'stopped' ? 'Остановлено' : 'Не запущен');
            lbl.style.color = d.status === 'running' ? 'var(--success)' :
                              (d.status === 'stopped' ? 'var(--warning)' : 'var(--text-muted)');
        }
        if (d.status !== 'running' && _ncTimer) {
            clearInterval(_ncTimer); _ncTimer = null;
            document.getElementById('btn-nc-start').style.display = '';
            document.getElementById('btn-nc-stop').style.display = 'none';
        }
        var c = d.counts || {};
        var counters = document.getElementById('nc-counters');
        if (counters) {
            counters.innerHTML =
                '<span style="color:var(--success);">✓ Прокомментировано: <b>' + (c.commented || 0) + '</b></span> · ' +
                '<span>Пропущено (уже комментили): ' + (c.skipped_already || 0) + '</span> · ' +
                '<span>Без свободных акков: ' + (c.skipped_no_acc || 0) + '</span> · ' +
                '<span style="color:var(--danger);">Ошибки: ' + (c.errors || 0) + '</span>';
        }
        var logEl = document.getElementById('nc-log');
        if (logEl && d.log) {
            logEl.innerHTML = d.log.map(function(l) {
                return '<div>' + l.replace(/&/g,'&amp;').replace(/</g,'&lt;') + '</div>';
            }).join('');
            logEl.scrollTop = logEl.scrollHeight;
        }
    });
}

// ── AI settings ─────────────────────────────────────────────────────────
var _aiProviders = {};

function aiLoad() {
    fetch('/api/ai/get').then(function(r){ return r.json(); }).then(function(d) {
        if (!d.ok) return;
        _aiProviders = d.providers || {};
        var s = d.settings || {};
        // Заполняем dropdown провайдеров
        var sel = document.getElementById('ai-provider');
        if (sel) {
            sel.innerHTML = '';
            Object.keys(_aiProviders).forEach(function(k) {
                var opt = document.createElement('option');
                opt.value = k;
                opt.textContent = _aiProviders[k].label;
                if (k === (s.provider || 'openrouter')) opt.selected = true;
                sel.appendChild(opt);
            });
        }
        onAiProviderChange();
        if (s.url) document.getElementById('ai-url').value = s.url;
        if (s.model) {
            var mSel = document.getElementById('ai-model');
            // Если модели нет в списке провайдера — добавим как custom
            var found = false;
            for (var i = 0; i < mSel.options.length; i++) {
                if (mSel.options[i].value === s.model) { mSel.value = s.model; found = true; break; }
            }
            if (!found && s.model) {
                var opt = document.createElement('option');
                opt.value = s.model; opt.textContent = s.model;
                mSel.appendChild(opt); mSel.value = s.model;
            }
        }
        document.getElementById('ai-budget-usd').value = s.daily_limit_usd || 0;
        document.getElementById('ai-budget-req').value = s.max_requests || 0;
        document.getElementById('ai-dialog-topics').value = s.dialog_topics || '';
        document.getElementById('ai-custom-dialog-prompt').value = s.custom_dialog_prompt || '';
        document.getElementById('ai-custom-comment-prompt').value = s.custom_comment_prompt || '';
        document.getElementById('ai-comment-cta').value = s.comment_cta || '';
        document.getElementById('ai-enabled').checked = !!s.enabled;
        var hasKey = document.getElementById('ai-has-key-badge');
        if (hasKey) hasKey.style.display = s.has_key ? '' : 'none';
        // Status badge + spent info
        var status = d.status || {};
        var badge = document.getElementById('ai-status-badge');
        if (badge) {
            if (status.enabled) {
                badge.className = 'badge-soft badge-soft--success';
                badge.textContent = 'активно: ' + (status.model || '');
            } else {
                badge.className = 'badge-soft badge-soft--muted';
                badge.textContent = 'не настроено';
            }
        }
        var spent = document.getElementById('ai-spent-info');
        if (spent) {
            if (status.enabled) {
                spent.textContent = 'Сегодня: $' + (status.spent_usd_today || 0).toFixed(4) +
                    ' (' + (status.requests_today || 0) + ' запросов)' +
                    (status.daily_limit_usd > 0 ? ' / лимит $' + status.daily_limit_usd.toFixed(2) : '');
            } else { spent.textContent = ''; }
        }
        // Quota warning + saport redirect
        try { _aiQuotaCheck(status); } catch (e) {}
    });
}

function onAiProviderChange() {
    var pid = (document.getElementById('ai-provider') || {}).value || 'openrouter';
    var prov = _aiProviders[pid] || {};
    var urlRow = document.getElementById('ai-url-row');
    if (urlRow) urlRow.style.display = pid === 'custom' ? '' : 'none';
    if (pid !== 'custom') {
        document.getElementById('ai-url').value = prov.url || '';
    }
    var mSel = document.getElementById('ai-model');
    if (mSel) {
        mSel.innerHTML = '';
        (prov.models || []).forEach(function(m) {
            var opt = document.createElement('option');
            opt.value = m; opt.textContent = m;
            mSel.appendChild(opt);
        });
        if (prov.default_model) mSel.value = prov.default_model;
        // Для custom — поле ввода вместо select
        if (pid === 'custom') {
            mSel.innerHTML = '<option value="">(введи в URL endpoint выше + перезайди)</option>';
        }
    }
    // Подсказка
    var hint = document.getElementById('ai-signup-hint');
    if (hint) {
        var lines = [];
        if (prov.signup) {
            lines.push('Получить ключ: <a href="' + prov.signup + '" target="_blank" style="color:var(--accent-light);">' + prov.signup + '</a>');
        }
        if (prov.hint) lines.push(prov.hint);
        if (prov.price_per_1m_in) {
            lines.push('Цена: $' + prov.price_per_1m_in + ' за 1М input-токенов / $' +
                       prov.price_per_1m_out + ' за 1М output-токенов');
        }
        hint.innerHTML = lines.join('<br>');
    }
}

function aiSave() {
    var key = (document.getElementById('ai-key') || {}).value || '';
    var payload = {
        enabled: document.getElementById('ai-enabled').checked,
        provider: document.getElementById('ai-provider').value,
        url: document.getElementById('ai-url').value,
        model: document.getElementById('ai-model').value,
        daily_limit_usd: parseFloat(document.getElementById('ai-budget-usd').value) || 0,
        max_requests: parseInt(document.getElementById('ai-budget-req').value) || 0,
        dialog_topics: document.getElementById('ai-dialog-topics').value,
        custom_dialog_prompt: document.getElementById('ai-custom-dialog-prompt').value,
        custom_comment_prompt: document.getElementById('ai-custom-comment-prompt').value,
        comment_cta: document.getElementById('ai-comment-cta').value,
    };
    if (key) payload.api_key = key;
    fetch('/api/ai/save', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(payload)
    }).then(function(r){return r.json()}).then(function(d) {
        if (d.ok) {
            showToast('AI-настройки сохранены', 'success');
            document.getElementById('ai-key').value = '';
            aiLoad();
        } else {
            showToast(d.error || 'Ошибка сохранения', 'error');
        }
    });
}

function aiTest() {
    var btn = document.getElementById('btn-ai-test');
    var resEl = document.getElementById('ai-test-result');
    if (resEl) resEl.innerHTML = '<i class="bi bi-arrow-repeat" style="animation:spin 1s linear infinite;display:inline-block;"></i> Проверяю...';
    if (btn) btn.disabled = true;
    var payload = {
        provider: document.getElementById('ai-provider').value,
        url: document.getElementById('ai-url').value,
        model: document.getElementById('ai-model').value,
        api_key: (document.getElementById('ai-key') || {}).value || '',
    };
    fetch('/api/ai/test', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(payload)
    }).then(function(r){return r.json()}).then(function(d) {
        if (btn) btn.disabled = false;
        if (d.ok) {
            resEl.innerHTML = '<span style="color:var(--success);">✓ Работает.</span> Модель: <code>' + d.model + '</code>. ' +
                'Токены in/out: ' + d.tokens_in + '/' + d.tokens_out + '. ' +
                'Стоимость теста: $' + (d.cost_usd || 0).toFixed(6) + '. ' +
                '<br><span style="color:var(--text-muted);">Ответ: «' + (d.reply || '') + '»</span>';
        } else {
            resEl.innerHTML = '<span style="color:var(--danger);">✗ ' + (d.error || 'Ошибка') + '</span>';
        }
    });
}

// ── AI Pools (generated chat names / usernames / greetings / diary posts) ──
var _aiPoolPoll = null;
var _cachedSupportUrl = null;

function _aiSupportLink() {
    // Саппорт-ссылка — тянем с /api/support_info и кешируем. Источник истины —
    // config.SUPPORT_TG_URL на сервере (через ENV TELEPUSH_SUPPORT_URL).
    // Если ещё не успели загрузить — дефолт, потом перезапишется.
    if (_cachedSupportUrl) return _cachedSupportUrl;
    fetch('/api/support_info').then(function(r){return r.json()}).then(function(d) {
        if (d && d.url) {
            _cachedSupportUrl = d.url;
            // Обновляем уже отрендеренные ссылки на саппорт
            var links = document.querySelectorAll('a[data-support-link]');
            for (var i = 0; i < links.length; i++) links[i].href = d.url;
        }
    }).catch(function(){});
    return _cachedSupportUrl || 'https://t.me/robi_telegram';
}

function loadAiPoolStatus() {
    var supportA = document.getElementById('ai-support-link');
    if (supportA) supportA.href = _aiSupportLink();
    API.get('/api/ai/pool_status').then(function(s) {
        var el = document.getElementById('ai-pool-status');
        var hintEl = document.getElementById('warmup-ai-pool-hint');
        if (s.empty) {
            if (el) el.innerHTML = '<span style="color:var(--text-muted);">Пулы не сгенерированы — используются встроенные шаблоны.</span>';
            if (hintEl) hintEl.textContent = 'AI-пулы пока не сгенерированы.';
        } else {
            var dtxt = '';
            try {
                var d = new Date(s.generated_at * 1000);
                dtxt = d.toLocaleString('ru-RU');
            } catch (e) { dtxt = '—'; }
            var diaryStr = Object.keys(s.diary || {}).map(function(k){return k+':'+s.diary[k]}).join(', ');
            if (el) {
                el.innerHTML = '<span style="color:var(--success);">✓ Пулы готовы.</span> ' +
                    'Имена: ' + s.chat_names + ' · юзернеймы: ' + s.usernames + ' · приветствия: ' + s.greetings +
                    ' · дневники: ' + diaryStr +
                    '<br><span style="color:var(--text-muted);font-size:0.65rem;">Сгенерировано ' + dtxt + ' (модель: ' + (s.model_used || '—') + ')</span>';
            }
            if (hintEl) hintEl.textContent = 'AI-пулы готовы (' + s.usernames + ' юзернеймов, ' + s.chat_names + ' имён, ' + s.greetings + ' приветствий).';
        }
        // Прогресс генерации (если идёт)
        var gen = s.generation || {};
        var pBlock = document.getElementById('ai-pool-progress');
        var pBar = document.getElementById('ai-pool-progress-bar');
        var pText = document.getElementById('ai-pool-progress-text');
        var btn = document.getElementById('btn-ai-regen');
        if (gen.running) {
            if (pBlock) pBlock.style.display = '';
            var pp = gen.progress || {};
            var pct = pp.total > 0 ? Math.round((pp.done / pp.total) * 100) : 0;
            if (pBar) pBar.style.width = pct + '%';
            if (pText) pText.textContent = (pp.label || '...') + ' — ' + pp.done + '/' + pp.total;
            if (btn) { btn.disabled = true; btn.innerHTML = '<i class="bi bi-arrow-repeat" style="animation:spin 1s linear infinite;display:inline-block;"></i> Генерирую...'; }
            // Поллим каждые 2 сек пока идёт
            if (!_aiPoolPoll) {
                _aiPoolPoll = setInterval(loadAiPoolStatus, 2000);
            }
        } else {
            if (_aiPoolPoll) { clearInterval(_aiPoolPoll); _aiPoolPoll = null; }
            if (pBlock) pBlock.style.display = 'none';
            if (btn) { btn.disabled = false; btn.innerHTML = '<i class="bi bi-magic"></i> Сгенерировать AI-пулы'; }
            // Показать результат последнего запуска если был
            var res = gen.result;
            if (res && !res.ok && res.error) {
                showToast('AI-пулы: ' + res.error, 'error');
            }
        }
    }).catch(function() {});
}

function aiRegeneratePools() {
    if (!confirm('Запустить AI-генерацию пулов? Займёт 1-3 минуты, потратит ~$0.05-0.15 на твоём ключе.')) return;
    API.post('/api/ai/regenerate_pools', {}).then(function(d) {
        if (d.ok) {
            showToast('Генерация AI-пулов запущена', 'success');
            loadAiPoolStatus();
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    }).catch(function() { showToast('Нет связи', 'error'); });
}

function aiClearPool() {
    if (!confirm('Сбросить сгенерированные AI-пулы? Прогрев вернётся к встроенным шаблонам.')) return;
    API.post('/api/ai/clear_pool', {}).then(function(d) {
        if (d.ok) {
            showToast('Пулы сброшены', 'success');
            loadAiPoolStatus();
        }
    });
}

// Quota warning: если бюджет исчерпан — показать блок с redirect на саппорт.
// Хук в aiLoad: после загрузки статуса проверяем и показываем/прячем.
function _aiQuotaCheck(status) {
    var warn = document.getElementById('ai-quota-warning');
    if (!warn) return;
    var limit = status.daily_limit_usd || 0;
    var spent = status.spent_usd_today || 0;
    var maxReq = status.max_requests || 0;
    var req = status.requests_today || 0;
    var depleted = (limit > 0 && spent >= limit) || (maxReq > 0 && req >= maxReq);
    warn.style.display = depleted ? '' : 'none';
}

function loadSettings() {
    aiLoad();
    loadAiPoolStatus();
    // Глобальные anti-detect настройки
    API.get('/api/global_ad/get').then(function(d) {
        var g = (d && d.global_ad) || {};
        var el = document.getElementById('global-pre-spam-check');
        if (el) el.checked = !!g.pre_spam_check;
    }).catch(function() {});
    // Расписание + TG-бот
    if (typeof loadSchedule === 'function') loadSchedule();
    if (typeof loadTgBot === 'function') loadTgBot();
    API.get('/api/settings').then(function(d) {
        // ВАЖНО: не дёргаем autosave на programmatic изменения value слайдеров,
        // иначе loadSettings() → setValue() → change → save → петля.
        // Отключаем флаг на время, привязку добавим после.
        window._settingsAutosaveBound = window._settingsAutosaveBound;  // сохраняем текущее
        if (d.join) {
            _setSlider('s-join-interval', d.join.interval);
            _setSlider('s-join-jitter', d.join.jitter);
            _setSlider('s-join-max', d.join.max_per_session);
            _setSlider('s-join-break', d.join.break_min);
            _setSlider('s-join-break-max', d.join.break_max);
        }
        if (d.spam) {
            _setSlider('s-spam-min', d.spam.min_delay);
            _setSlider('s-spam-max', d.spam.max_delay);
            _setSlider('s-spam-burst', d.spam.burst_size);
            _setSlider('s-spam-burst-pause-min', d.spam.burst_pause_min);
            _setSlider('s-spam-burst-pause-max', d.spam.burst_pause_max);
        }
        if (d.dm) {
            _setSlider('s-dm-min', d.dm.min_delay);
            _setSlider('s-dm-max', d.dm.max_delay);
            _setSlider('s-dm-burst', d.dm.burst_size);
            _setSlider('s-dm-burst-pause-min', d.dm.burst_pause_min);
            _setSlider('s-dm-burst-pause-max', d.dm.burst_pause_max);
        }
        if (d.api_id) { var el = document.getElementById('s-api-id'); if (el) el.value = d.api_id; }
        if (d.api_hash) { var el = document.getElementById('s-api-hash'); if (el) el.value = d.api_hash; }
        // Подключаем автосохранение ПОСЛЕ установки значений, чтобы loadSettings не
        // триггерил сохранение. Сбрасываем таймер если он был поставлен событиями во
        // время _setSlider.
        if (_autosaveTimer) { clearTimeout(_autosaveTimer); _autosaveTimer = null; }
        bindSettingsAutosave();
    });
}

function _setSlider(id, val) {
    var el = document.getElementById(id);
    if (el) {
        el.value = val;
        var valEl = document.getElementById('v-' + id.replace('s-', ''));
        if (valEl) valEl.textContent = val;
    }
}

function openSettingsFolder() {
    API.post('/api/settings/open_folder', {}).then(function(d) {
        if (d.ok) {
            showToast('Папка открыта: ' + d.path, 'success');
        } else {
            showToast('Не удалось открыть: ' + (d.error || '?') + '\nПуть: ' + (d.path || '?'), 'error');
        }
    }).catch(function(e) {
        showToast('Ошибка: ' + e, 'error');
    });
}

function showSettingsDebug() {
    API.get('/api/settings/debug').then(function(d) {
        var lines = [];
        lines.push('📁 DATA_DIR: ' + d.data_dir);
        lines.push('📄 Файл настроек: ' + d.settings_path);
        lines.push('   существует: ' + (d.settings_exists ? 'да' : 'нет'));
        if (d.settings_exists) {
            lines.push('   размер: ' + (d.settings_size || 0) + ' байт');
            lines.push('   изменён: ' + (d.settings_mtime || '?'));
            lines.push('   ключей: ' + ((d.settings_keys || []).length));
            lines.push('   spam_min_delay: ' + d.spam_min_delay);
            lines.push('   spam_max_delay: ' + d.spam_max_delay);
            if (d.settings_error) lines.push('   ошибка: ' + d.settings_error);
        }
        lines.push('');
        lines.push('💾 Бэкап: ' + d.backup_path);
        lines.push('   существует: ' + (d.backup_exists ? 'да' : 'нет'));
        if (d.backup_exists) {
            lines.push('   размер: ' + (d.backup_size || 0) + ' байт');
            lines.push('   изменён: ' + (d.backup_mtime || '?'));
            lines.push('   ключей: ' + d.backup_keys);
            lines.push('   spam_min_delay: ' + d.backup_spam_min);
        }
        lines.push('');
        lines.push('🖥 exe: ' + d.sys_executable);
        lines.push('🌍 env TELEPUSH_DATA_DIR: ' + (d.env_data_dir || '(не задан)'));
        lines.push('📂 cwd: ' + d.cwd);
        if (d.settings_readonly) {
            lines.push('');
            lines.push('[!] ФАЙЛ НАСТРОЕК ТОЛЬКО ДЛЯ ЧТЕНИЯ — снимите атрибут read-only!');
        }
        if (typeof d.telepush_processes_count !== 'undefined') {
            lines.push('');
            lines.push('🔎 Запущено процессов TelePush: ' + d.telepush_processes_count);
            lines.push('   (несколько копий в разных папках — норма, у каждой своя data/)');
            if (d.telepush_processes) {
                d.telepush_processes.forEach(function(pr) {
                    lines.push('   PID ' + pr.pid + ': ' + pr.exe);
                });
            }
        }
        alert(lines.join('\n'));
    }).catch(function(e) {
        alert('Ошибка диагностики: ' + e);
    });
}

function restoreSettingsFromBackup() {
    if (!confirm('Восстановить настройки из резервной копии? Текущие значения будут заменены.')) return;
    API.post('/api/settings/restore_from_backup', {}).then(function(d) {
        if (d.ok) {
            showToast('Восстановлено ' + d.keys + ' ключей. Обновите страницу.', 'success');
            setTimeout(function(){ loadSettings(); }, 500);
        } else {
            showToast(d.error || 'Не удалось восстановить', 'error');
        }
    }).catch(function(e) {
        showToast('Ошибка: ' + e, 'error');
    });
}

function saveSettings() {
    function _v(id) {
        var el = document.getElementById(id);
        return el ? parseInt(el.value) : null;
    }
    var data = {
        join_min_interval: _v('s-join-interval'),
        join_jitter: _v('s-join-jitter'),
        join_max_per_session: _v('s-join-max'),
        join_session_break_min: _v('s-join-break'),
        join_session_break_max: _v('s-join-break-max'),
        spam_min_delay: _v('s-spam-min'),
        spam_max_delay: _v('s-spam-max'),
        spam_burst_size: _v('s-spam-burst'),
        spam_burst_pause_min: _v('s-spam-burst-pause-min'),
        spam_burst_pause_max: _v('s-spam-burst-pause-max'),
        dm_min_delay: _v('s-dm-min'),
        dm_max_delay: _v('s-dm-max'),
        dm_burst_size: _v('s-dm-burst'),
        dm_burst_pause_min: _v('s-dm-burst-pause-min'),
        dm_burst_pause_max: _v('s-dm-burst-pause-max'),
    };
    // Убираем null-ключи (на случай отсутствия slider в DOM — старые шаблоны)
    Object.keys(data).forEach(function(k) { if (data[k] === null) delete data[k]; });
    // Extended settings
    var proxyTimeout = document.getElementById('s-proxy-timeout');
    if (proxyTimeout) data.proxy_timeout = parseInt(proxyTimeout.value);
    var connectRetries = document.getElementById('s-connect-retries');
    if (connectRetries) data.connect_retries = parseInt(connectRetries.value);
    var noProxyWeb = document.getElementById('s-no-proxy-web');
    if (noProxyWeb) data.no_proxy_web = noProxyWeb.checked ? 1 : 0;
    var connectDelay = document.getElementById('s-connect-delay');
    if (connectDelay) data.connect_delay = parseInt(connectDelay.value);
    var apiId = document.getElementById('s-api-id');
    if (apiId) data.api_id = parseInt(apiId.value);
    var apiHash = document.getElementById('s-api-hash');
    if (apiHash) data.api_hash = apiHash.value.trim();
    var shareEl = document.getElementById('s-share-parsed');
    if (shareEl) data.share_parsed_with_server = shareEl.checked ? 1 : 0;

    API.post('/api/settings', data).then(function(d) {
        if (d.ok) {
            _flashSettingsSaved(d.keys || '?');
        } else {
            showToast('Не сохранилось: ' + (d.error || 'неизвестная ошибка'), 'error');
        }
    }).catch(function(e) {
        // API ошибки приходят объектом {error, ...} — достаём текстом, не toString-им объект
        var msg = (e && (e.error || e.message)) ? (e.error || e.message) : (typeof e === 'string' ? e : JSON.stringify(e));
        showToast('Ошибка сохранения: ' + msg, 'error');
    });
}

// Короткий флэш «Сохранено» у кнопки «Сохранить настройки» — чтобы клиент
// видел что autosave отработал, но без спама тостов на каждый клик слайдера.
function _flashSettingsSaved(keys) {
    var btn = document.querySelector('#tab-settings button[onclick*="saveSettings"]');
    if (!btn) {
        showToast('Настройки сохранены (' + keys + ' ключей)', 'success');
        return;
    }
    var orig = btn.innerHTML;
    if (btn._flashRestoreTimer) clearTimeout(btn._flashRestoreTimer);
    btn.innerHTML = '<i class="bi bi-check2-circle"></i> Сохранено (' + keys + ')';
    btn.style.background = 'var(--success, #34d399)';
    btn._flashRestoreTimer = setTimeout(function() {
        btn.innerHTML = orig;
        btn.style.background = '';
    }, 1800);
}

// Авто-сохранение настроек: привязываем debounced save ко всем контролам на вкладке.
// Клиент двигает слайдер → через 800 мс настройки сами уходят на сервер.
// Раньше надо было жать «Сохранить настройки» — половина юзеров про это забывала.
var _autosaveTimer = null;
function _scheduleAutosaveSettings() {
    if (_autosaveTimer) clearTimeout(_autosaveTimer);
    _autosaveTimer = setTimeout(function() {
        saveSettings();
    }, 800);
}

function bindSettingsAutosave() {
    if (window._settingsAutosaveBound) return;
    var sec = document.getElementById('tab-settings');
    if (!sec) return;
    var inputs = sec.querySelectorAll('input, select, textarea');
    for (var i = 0; i < inputs.length; i++) {
        var el = inputs[i];
        var tag = (el.tagName || '').toLowerCase();
        var type = (el.type || '').toLowerCase();
        // Для текстовых/числовых — change (когда юзер закончил ввод). Для слайдеров/чекбоксов/селектов — input
        if (type === 'checkbox' || type === 'radio' || tag === 'select' || type === 'range') {
            el.addEventListener('change', _scheduleAutosaveSettings);
            el.addEventListener('input', _scheduleAutosaveSettings);
        } else {
            el.addEventListener('change', _scheduleAutosaveSettings);
        }
    }
    window._settingsAutosaveBound = true;
}


var _devicePresets = {
    desktop_2040: { api_id: 2040, api_hash: 'b18441a1ff607e10a989891a5462e627' },
    android:      { api_id: 4,    api_hash: '014b35b6184100b085b0d0572f9b5103' },
    ios:          { api_id: 8,    api_hash: '7245de8e747a0d6fbe11f7cc14fcc0bb' }
};

function onDeviceChange() {
    var sel = document.getElementById('s-json-device');
    if (!sel) return;
    var preset = _devicePresets[sel.value];
    if (preset) {
        var idEl = document.getElementById('s-api-id');
        var hashEl = document.getElementById('s-api-hash');
        if (idEl) idEl.value = preset.api_id;
        if (hashEl) hashEl.value = preset.api_hash;
    }
}

function toggleSettingsSection(id) {
    var el = document.getElementById(id);
    var chev = document.getElementById('chev-' + id);
    if (!el) return;
    if (el.style.display === 'none') {
        el.style.display = 'block';
        if (chev) { chev.classList.remove('bi-chevron-down'); chev.classList.add('bi-chevron-up'); }
    } else {
        el.style.display = 'none';
        if (chev) { chev.classList.remove('bi-chevron-up'); chev.classList.add('bi-chevron-down'); }
    }
}

function packLogs() {
    showToast('Упаковка логов...', 'info');
    API.post('/api/logs/pack').then(function(d) {
        if (d.ok) {
            var msg = 'Логи зашифрованы: ' + (d.file || 'logs.tplog');
            showToast(msg, 'success');
            if (typeof addLog === 'function') {
                addLog('Зашифрованный архив: ' + (d.path || d.file), 'INFO');
                addLog('Файл .tplog — зашифрован AES-256, прочитать может только разработчик. Безопасно пересылать в поддержку.', 'INFO');
            }
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    }).catch(function() { showToast('Ошибка упаковки', 'error'); });
}

// ══════════════════════════════════════════════════════════════════
//  TICKET — поддержка
// ══════════════════════════════════════════════════════════════════

function previewTicketImg(input) {
    var preview = document.getElementById('ticket-img-preview');
    var thumb = document.getElementById('ticket-img-thumb');
    if (input.files && input.files[0]) {
        var reader = new FileReader();
        reader.onload = function(e) { thumb.src = e.target.result; preview.style.display = 'block'; };
        reader.readAsDataURL(input.files[0]);
    }
}
function clearTicketImg() {
    document.getElementById('ticket-screenshot').value = '';
    document.getElementById('ticket-img-preview').style.display = 'none';
}

function copyDiagInfo(btn) {
    fetch('/api/diag').then(function(r) { return r.json(); }).then(function(d) {
        // Форматируем в человекочитаемый текст для вставки в саппорт.
        var lines = [];
        lines.push('=== TelePush диагностика ' + (d.ts || '') + ' ===');
        lines.push('Версия: ' + (d.version || '?'));
        lines.push('ОС: ' + (d.platform || '?'));
        lines.push('Python: ' + (d.python || '?'));
        if (d.license_status) lines.push('Лицензия: ' + d.license_status + (d.license_expires ? ' (до ' + d.license_expires + ')' : ''));
        if (d.license_key_mask) lines.push('Ключ: ' + d.license_key_mask);
        if (d.hwid_mask) lines.push('HWID: ' + d.hwid_mask);
        lines.push('');
        lines.push('Аккаунты: ' + (d.accounts_total || 0) + ' (мёртвых: ' + (d.accounts_dead || 0) +
            ', замороженных: ' + (d.accounts_frozen || 0) + ', floodwait: ' + (d.accounts_floodwait || 0) +
            ', занято модулями: ' + (d.accounts_busy || 0) + ')');
        if (d.accounts_dir_size_mb) lines.push('Размер папки Аккаунты: ' + d.accounts_dir_size_mb + ' МБ');
        if (typeof d.proxies_total === 'number') lines.push('Прокси: ' + d.proxies_alive + '/' + d.proxies_total + ' живых');
        if (d.active_jobs && d.active_jobs.length) {
            lines.push('');
            lines.push('Активные задачи:');
            d.active_jobs.forEach(function(j) {
                lines.push('  - ' + j.type + ' [' + j.status + '] ' + (j.processed || 0) + '/' + (j.total || 0));
            });
        }
        if (d.recent_errors && d.recent_errors.length) {
            lines.push('');
            lines.push('Последние ошибки:');
            d.recent_errors.forEach(function(e) { lines.push('  ' + e); });
        }
        var txt = lines.join('\n');
        // Копируем через clipboard API с фоллбэком на textarea-select.
        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(txt).then(function() {
                if (btn) {
                    var orig = btn.innerHTML;
                    btn.innerHTML = '<i class="bi bi-check-lg"></i> Скопировано!';
                    setTimeout(function() { btn.innerHTML = orig; }, 2000);
                }
                showToast('Диагностика скопирована (' + txt.length + ' символов). Вставьте в обращение.', 'success');
            }, function() {
                _diagFallbackCopy(txt);
            });
        } else {
            _diagFallbackCopy(txt);
        }
    }).catch(function() {
        showToast('Не удалось получить диагностику', 'error');
    });
}

function _diagFallbackCopy(txt) {
    var ta = document.createElement('textarea');
    ta.value = txt;
    ta.style.position = 'fixed';
    ta.style.opacity = '0';
    document.body.appendChild(ta);
    ta.select();
    try {
        document.execCommand('copy');
        showToast('Диагностика скопирована', 'success');
    } catch (e) {
        showToast('Не удалось скопировать. Скопируйте вручную из консоли.', 'error');
        console.log(txt);
    }
    document.body.removeChild(ta);
}

function sendTicket() {
    var module = (document.getElementById('ticket-module') || {}).value || '';
    var subject = (document.getElementById('ticket-subject') || {}).value.trim();
    var body = (document.getElementById('ticket-body') || {}).value.trim();
    var fileInput = document.getElementById('ticket-screenshot');

    if (!module) { showToast('Выберите модуль', 'warning'); return; }
    if (!subject) { showToast('Введите тему', 'warning'); return; }
    if (!body) { showToast('Опишите проблему', 'warning'); return; }

    var btn = document.getElementById('btn-send-ticket');
    if (btn) { btn.disabled = true; btn.innerHTML = '<i class="bi bi-hourglass-split"></i> Отправка...'; }

    var fd = new FormData();
    fd.append('module', module);
    fd.append('subject', subject);
    fd.append('body', body);
    if (fileInput && fileInput.files.length) {
        fd.append('screenshot', fileInput.files[0]);
    }

    fetch('/api/ticket', {method: 'POST', body: fd}).then(function(r) { return r.json(); }).then(function(d) {
        if (btn) { btn.disabled = false; btn.innerHTML = '<i class="bi bi-send"></i> Отправить'; }
        var res = document.getElementById('ticket-result');
        if (d.ok) {
            showToast('Тикет отправлен!', 'success');
            document.getElementById('ticket-subject').value = '';
            document.getElementById('ticket-body').value = '';
            document.getElementById('ticket-module').value = '';
            clearTicketImg();
            if (res) { res.style.display = 'block'; res.style.color = 'var(--success)'; res.textContent = 'Отправлено! Ответим в ближайшее время.'; }
        } else {
            if (res) { res.style.display = 'block'; res.style.color = 'var(--danger)'; res.textContent = d.error || 'Ошибка'; }
        }
    }).catch(function() {
        if (btn) { btn.disabled = false; btn.innerHTML = '<i class="bi bi-send"></i> Отправить'; }
        showToast('Ошибка отправки', 'error');
    });
}

// ── Update checker ──────────────────────────────────────────────

function checkUpdate() {
    API.get('/api/update/check').then(function(d) {
        if (d.available) {
            var banner = document.getElementById('update-banner');
            var ver = document.getElementById('update-version');
            var cl = document.getElementById('update-changelog');
            if (banner) banner.style.display = 'flex';
            if (ver) ver.textContent = 'v' + d.version;
            if (cl && d.changelog) cl.textContent = d.changelog;

            if (d.force_download) {
                var btn = document.querySelector('#update-banner .btn');
                if (btn) {
                    btn.onclick = function() { window.open(d.download_url || 'https://telepush.org', '_blank'); };
                    btn.innerHTML = '<i class="bi bi-box-arrow-up-right"></i> Скачать с сайта';
                }
            }
        }
    }).catch(function() {});
}
// Проверяем обновления каждые 2 минуты
setInterval(checkUpdate, 120000);

// ── Broadcast (объявления от админа) ─────────────────────────────────────────
var _BC_LEVELS = {
    info:     { color: '#3b82f6', bg: 'rgba(59,130,246,0.10)', border: 'rgba(59,130,246,0.45)', icon: 'bi-info-circle-fill' },
    success:  { color: '#22c55e', bg: 'rgba(34,197,94,0.10)',  border: 'rgba(34,197,94,0.45)',  icon: 'bi-check-circle-fill' },
    warning:  { color: '#eab308', bg: 'rgba(234,179,8,0.10)',  border: 'rgba(234,179,8,0.45)',  icon: 'bi-exclamation-triangle-fill' },
    critical: { color: '#ef4444', bg: 'rgba(239,68,68,0.12)',  border: 'rgba(239,68,68,0.50)',  icon: 'bi-megaphone-fill' }
};

function pollBroadcast() {
    fetch('/api/broadcast/check').then(function(r) { return r.json(); }).then(function(d) {
        var ann = d && d.announcement;
        var banner = document.getElementById('broadcast-banner');
        if (!banner) return;
        if (!ann) {
            banner.style.display = 'none';
            return;
        }
        // Проверяем что юзер не закрыл именно это объявление
        var dismissedId = null;
        try { dismissedId = localStorage.getItem('broadcast_dismissed_id'); } catch (e) {}
        if (dismissedId && String(dismissedId) === String(ann.id)) {
            banner.style.display = 'none';
            return;
        }
        var lvl = _BC_LEVELS[ann.level] || _BC_LEVELS.info;
        banner.style.background = lvl.bg;
        banner.style.borderColor = lvl.border;
        banner.style.color = lvl.color;
        var ic = document.getElementById('broadcast-icon');
        if (ic) {
            ic.className = 'bi ' + lvl.icon;
            ic.style.color = lvl.color;
        }
        var t = document.getElementById('broadcast-title');
        if (t) {
            t.textContent = ann.title || '';
            t.style.display = ann.title ? 'block' : 'none';
        }
        var txt = document.getElementById('broadcast-text');
        if (txt) txt.textContent = ann.text || '';
        banner.dataset.bcId = String(ann.id);
        banner.style.display = 'block';
    }).catch(function() {});
}

function dismissBroadcast() {
    var banner = document.getElementById('broadcast-banner');
    if (!banner) return;
    var id = banner.dataset.bcId;
    if (id) {
        try { localStorage.setItem('broadcast_dismissed_id', id); } catch (e) {}
    }
    banner.style.display = 'none';
}

// Сразу опрашиваем при загрузке + раз в 5 минут
setTimeout(pollBroadcast, 2000);
setInterval(pollBroadcast, 300000);

function installUpdate() {
    showConfirmDialog(
        'Установить обновление?',
        'Все активные задачи будут остановлены. Софт перезапустится автоматически.',
        function() {
            showToast('Скачиваю обновление... Не закрывайте программу!', 'info');
            _doInstallUpdate();
        }
    );
}

function _doInstallUpdate() {
    var btn = document.querySelector('#update-banner .btn');
    if (btn) { btn.disabled = true; btn.innerHTML = '<i class="bi bi-arrow-repeat" style="animation:spin 1s linear infinite;display:inline-block;"></i> Скачиваю...'; }

    // Запрос на установку может занять 30-120 сек (скачивание + распаковка).
    // fetch тут длинный — пользуемся API.post, у него свой таймаут может быть выставлен.
    API.post('/api/update/install').then(function(d) {
        if (d && d.ok) {
            showToast('Обновление установлено! Перезапуск через 2 сек...', 'success');
            return;  // скоро процесс убьётся через os._exit + лаунчер
        }
        var msg = (d && d.message) || (d && d.error) || 'Неизвестная ошибка';
        _renderUpdateError(msg, (d && d.manual_url) || 'https://telepush.org/api/download?v=latest', btn);
    }).catch(function(err) {
        var msg = 'Не удалось связаться с Flask-сервером (программа зависла?). Перезапусти TelePush или скачай вручную.';
        _renderUpdateError(msg, 'https://telepush.org/api/download?v=latest', btn);
    });
}

function _renderUpdateError(msg, manualUrl, btn) {
    showToast('Ошибка обновления: ' + msg, 'error');
    try {
        if (typeof addLog === 'function') {
            addLog('Ошибка обновления: ' + msg, 'ERROR');
            addLog('Скачать вручную: ' + manualUrl + ' — распакуй поверх папки TelePush, папку data/ не трогать', 'INFO');
        }
    } catch (_) {}
    if (btn) { btn.disabled = false; btn.innerHTML = '<i class="bi bi-download"></i> Обновить'; }
    // Показываем кнопку «Скачать вручную» рядом с основной
    var banner = document.getElementById('update-banner');
    if (banner && !document.getElementById('update-manual-btn')) {
        var a = document.createElement('a');
        a.id = 'update-manual-btn';
        a.href = manualUrl;
        a.target = '_blank';
        a.rel = 'noopener';
        a.className = 'btn btn-outline btn-sm';
        a.style.cssText = 'margin-left:8px;white-space:nowrap;';
        a.innerHTML = '<i class="bi bi-box-arrow-up-right"></i> Скачать вручную';
        if (btn && btn.parentNode) btn.parentNode.insertBefore(a, btn.nextSibling);
    }
}

// ── Telegram Messenger Panel ────────────────────────────────────

var _tgPanelOpen = false;
var _tgCurrentChat = null;

function toggleTgPanel() {
    _tgPanelOpen = !_tgPanelOpen;
    var panel = document.getElementById('tg-panel');
    if (panel) panel.style.right = _tgPanelOpen ? '0' : '-420px';
    if (_tgPanelOpen) loadTgDialogs();
}

function _tgSetHeader(title, sub, showBack) {
    var t = document.getElementById('tg-header-title');
    var s = document.getElementById('tg-header-sub');
    var b = document.getElementById('tg-back-btn');
    var icon = document.getElementById('tg-header-icon');
    var tabs = document.getElementById('tg-tabs');
    var info = document.getElementById('tg-info-btn');
    if (t) t.textContent = title;
    if (s) { s.textContent = sub || ''; s.style.display = sub ? 'block' : 'none'; }
    if (b) b.style.display = showBack ? '' : 'none';
    if (icon) icon.style.display = showBack ? 'none' : '';
    if (tabs) tabs.style.display = showBack ? 'none' : 'flex';
    if (info) info.style.display = showBack ? '' : 'none';
}

function showTgUserInfo() {
    if (!_tgCurrentChat) return;
    var modal = document.createElement('div');
    modal.id = 'tg-user-modal';
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:500;display:flex;align-items:center;justify-content:center;';
    modal.onclick = function(e) { if (e.target === modal) modal.remove(); };
    modal.innerHTML = '<div style="background:var(--bg-card-hover);border:1px solid var(--border);border-radius:14px;padding:20px;max-width:380px;width:90%;max-height:85vh;overflow-y:auto;"><div style="text-align:center;color:var(--text-muted);padding:24px;"><i class="bi bi-arrow-repeat" style="animation:spin 1s linear infinite;font-size:1.4rem;"></i></div></div>';
    document.body.appendChild(modal);

    API.get('/api/tg/user_info?chat_id=' + _tgCurrentChat).then(function(d) {
        var box = modal.querySelector('div');
        if (!box) return;
        if (d.error) {
            box.innerHTML = '<div style="color:#f87171;padding:20px;text-align:center;">Ошибка: ' + escHtml(d.error) + '</div>' +
                '<button onclick="document.getElementById(\'tg-user-modal\').remove()" style="width:100%;padding:8px;background:var(--accent);border:none;border-radius:8px;color:#fff;cursor:pointer;">Закрыть</button>';
            return;
        }
        var html = '';
        var avatarHtml = '<div style="text-align:center;margin-bottom:16px;"><img src="/api/tg/avatar?chat_id=' + _tgCurrentChat + '" style="width:90px;height:90px;border-radius:50%;object-fit:cover;border:2px solid #252540;" onerror="this.style.display=\'none\'"></div>';
        if (d.type === 'user') {
            var name = (d.first_name + ' ' + d.last_name).trim() || 'Без имени';
            var badges = '';
            if (d.is_premium) badges += '<span style="display:inline-block;background:#fef3c7;color:#92400e;padding:2px 6px;border-radius:4px;font-size:0.65rem;font-weight:600;margin-right:4px;"><i class="bi bi-star-fill"></i> Premium</span>';
            if (d.is_verified) badges += '<span style="display:inline-block;background:#dbeafe;color:#1e40af;padding:2px 6px;border-radius:4px;font-size:0.65rem;font-weight:600;margin-right:4px;">✓ Verified</span>';
            if (d.is_bot) badges += '<span style="display:inline-block;background:#ede9fe;color:#5b21b6;padding:2px 6px;border-radius:4px;font-size:0.65rem;font-weight:600;margin-right:4px;">Bot</span>';
            if (d.is_scam) badges += '<span style="display:inline-block;background:#fee2e2;color:#991b1b;padding:2px 6px;border-radius:4px;font-size:0.65rem;font-weight:600;margin-right:4px;"><i class="bi bi-exclamation-triangle-fill"></i> Scam</span>';
            if (d.is_fake) badges += '<span style="display:inline-block;background:#fee2e2;color:#991b1b;padding:2px 6px;border-radius:4px;font-size:0.65rem;font-weight:600;margin-right:4px;"><i class="bi bi-exclamation-triangle-fill"></i> Fake</span>';
            html = avatarHtml +
                '<h3 style="text-align:center;color:#fff;font-size:1.1rem;margin-bottom:4px;">' + escHtml(name) + '</h3>' +
                (d.username ? '<div style="text-align:center;color:var(--accent-light);font-size:0.82rem;margin-bottom:10px;">@' + escHtml(d.username) + '</div>' : '') +
                (badges ? '<div style="text-align:center;margin-bottom:14px;">' + badges + '</div>' : '') +
                '<div style="background:var(--bg-input);border-radius:8px;padding:12px;margin-bottom:12px;font-size:0.78rem;">' +
                    '<div style="display:flex;justify-content:space-between;margin-bottom:6px;"><span style="color:var(--text-muted);">ID</span><span style="color:#fff;font-family:monospace;">' + d.id + '</span></div>' +
                    (d.phone ? '<div style="display:flex;justify-content:space-between;margin-bottom:6px;"><span style="color:var(--text-muted);">Телефон</span><span style="color:#fff;">+' + escHtml(d.phone) + '</span></div>' : '') +
                    (d.common_chats ? '<div style="display:flex;justify-content:space-between;"><span style="color:var(--text-muted);">Общих чатов</span><span style="color:#fff;">' + d.common_chats + '</span></div>' : '') +
                '</div>' +
                (d.about ? '<div style="background:var(--bg-input);border-radius:8px;padding:12px;margin-bottom:12px;"><div style="color:var(--text-muted);font-size:0.7rem;margin-bottom:4px;text-transform:uppercase;">О себе</div><div style="color:#fff;font-size:0.82rem;white-space:pre-wrap;">' + escHtml(d.about) + '</div></div>' : '');
        } else {
            html = avatarHtml +
                '<h3 style="text-align:center;color:#fff;font-size:1.1rem;margin-bottom:4px;">' + escHtml(d.title || '') + '</h3>' +
                (d.username ? '<div style="text-align:center;color:var(--accent-light);font-size:0.82rem;margin-bottom:10px;">@' + escHtml(d.username) + '</div>' : '') +
                '<div style="background:var(--bg-input);border-radius:8px;padding:12px;margin-bottom:12px;font-size:0.78rem;">' +
                    '<div style="display:flex;justify-content:space-between;margin-bottom:6px;"><span style="color:var(--text-muted);">ID</span><span style="color:#fff;font-family:monospace;">' + d.id + '</span></div>' +
                    '<div style="display:flex;justify-content:space-between;margin-bottom:6px;"><span style="color:var(--text-muted);">Тип</span><span style="color:#fff;">' + (d.type === 'channel' ? 'Канал' : 'Группа') + '</span></div>' +
                    (d.participants_count ? '<div style="display:flex;justify-content:space-between;"><span style="color:var(--text-muted);">Участников</span><span style="color:#fff;">' + d.participants_count + '</span></div>' : '') +
                '</div>' +
                (d.about ? '<div style="background:var(--bg-input);border-radius:8px;padding:12px;margin-bottom:12px;"><div style="color:var(--text-muted);font-size:0.7rem;margin-bottom:4px;text-transform:uppercase;">Описание</div><div style="color:#fff;font-size:0.82rem;white-space:pre-wrap;">' + escHtml(d.about) + '</div></div>' : '');
        }
        html += '<button onclick="document.getElementById(\'tg-user-modal\').remove()" style="width:100%;padding:10px;background:var(--accent);border:none;border-radius:8px;color:#fff;cursor:pointer;font-weight:600;">Закрыть</button>';
        box.innerHTML = html;
    });
}

function tgShowTab(btn, tab) {
    document.querySelectorAll('.tg-tab').forEach(function(b) {
        b.style.color = '#64748b';
        b.style.borderBottomColor = 'transparent';
    });
    btn.style.color = '#a78bfa';
    btn.style.borderBottomColor = '#7c3aed';
    ['chats','replies','log'].forEach(function(t) {
        var el = document.getElementById('tg-' + t);
        if (el) el.style.display = t === tab ? 'block' : 'none';
    });
    _tgSetHeader('Telegram', '', false);
    _tgCurrentChat = null;
    if (tab === 'chats') loadTgDialogs();
    if (tab === 'replies') loadTgReplies();
}

function loadTgDialogs() {
    _tgCurrentChat = null;
    _tgSetHeader('Telegram', '', false);
    var tabs = document.getElementById('tg-tabs');
    if (tabs) tabs.style.display = 'flex';

    API.get('/api/tg/dialogs').then(function(d) {
        var el = document.getElementById('tg-chats');
        if (!el) return;
        if (d.error) {
            el.innerHTML = '<div style="text-align:center;color:#f59e0b;padding:32px;font-size:0.8rem;"><i class="bi bi-exclamation-triangle" style="font-size:1.5rem;display:block;margin-bottom:8px;"></i>' + escHtml(d.error) + '</div>';
            return;
        }
        if (d.busy) {
            el.innerHTML = '<div style="text-align:center;color:#f59e0b;padding:32px;font-size:0.8rem;"><i class="bi bi-lock" style="font-size:1.5rem;display:block;margin-bottom:8px;"></i>' + escHtml(d.msg || 'Аккаунт занят задачей') + '</div>';
            return;
        }
        if (!d.dialogs || !d.dialogs.length) {
            el.innerHTML = '<div style="text-align:center;color:#64748b;padding:32px;"><i class="bi bi-chat-square-text" style="font-size:1.5rem;display:block;margin-bottom:8px;"></i>Нет чатов</div>';
            return;
        }
        el.innerHTML = d.dialogs.map(function(c) {
            var avatarBg = c.type === 'user' ? '#3b82f6' : c.type === 'channel' ? '#f59e0b' : '#22c55e';
            var initial = (c.name || '?').charAt(0).toUpperCase();
            var unread = c.unread > 0 ? '<span style="background:var(--accent);color:#fff;border-radius:10px;padding:2px 7px;font-size:0.6rem;font-weight:700;min-width:18px;text-align:center;">' + c.unread + '</span>' : '';
            var lastMsg = c.last_message || '';
            var timeStr = '';
            if (c.date) {
                try { var dt = new Date(c.date); timeStr = dt.getHours() + ':' + (dt.getMinutes()<10?'0':'') + dt.getMinutes(); } catch(e) {}
            }
            // data-атрибуты вместо inline onclick — имена с кавычками/эмодзи/апострофами
            // раньше ломали JS-строку и чат просто не открывался.
            return '<div class="tg-chat-item" data-chat-id="' + c.id + '" data-chat-type="' + escHtml(c.type) + '" data-chat-name="' + escHtml(c.name) + '" style="padding:10px 12px;border-bottom:1px solid #1a1a2e;cursor:pointer;transition:background 0.15s;">' +
                '<div style="display:flex;align-items:center;gap:10px;pointer-events:none;">' +
                    '<div style="width:40px;height:40px;border-radius:50%;background:' + avatarBg + ';display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:700;font-size:0.9rem;color:#fff;overflow:hidden;position:relative;">' +
                        '<img src="/api/tg/avatar?chat_id=' + c.id + '" style="width:100%;height:100%;object-fit:cover;position:absolute;top:0;left:0;" onerror="this.style.display=\'none\'">' +
                        initial +
                    '</div>' +
                    '<div style="flex:1;min-width:0;">' +
                        '<div style="display:flex;justify-content:space-between;align-items:center;">' +
                            '<span style="font-size:0.82rem;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:200px;">' + escHtml(c.name) + '</span>' +
                            '<span style="font-size:0.6rem;color:var(--text-muted);flex-shrink:0;">' + timeStr + '</span>' +
                        '</div>' +
                        '<div style="display:flex;justify-content:space-between;align-items:center;margin-top:2px;">' +
                            '<span style="font-size:0.72rem;color:#64748b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px;">' + escHtml(lastMsg) + '</span>' +
                            unread +
                        '</div>' +
                    '</div>' +
                '</div>' +
            '</div>';
        }).join('');
        // Делегированный handler — один раз на контейнер, берём атрибуты из элемента
        if (!el._tgClickBound) {
            el.addEventListener('click', function(ev) {
                var item = ev.target.closest('.tg-chat-item');
                if (!item) return;
                var cid = parseInt(item.getAttribute('data-chat-id'));
                var cname = item.getAttribute('data-chat-name') || '';
                var ctype = item.getAttribute('data-chat-type') || 'user';
                if (!isNaN(cid)) loadTgMessages(cid, cname, ctype);
            });
            el._tgClickBound = true;
        }
        // Hover-эффект через CSS вместо inline
        el.querySelectorAll('.tg-chat-item').forEach(function(it) {
            it.onmouseenter = function() { this.style.background = '#161028'; };
            it.onmouseleave = function() { this.style.background = 'transparent'; };
        });
    }).catch(function() {});
}

function _tgRenderMedia(m) {
    if (!m.media) return '';
    var chatId = m.chat_id;
    var msgId = m.id;
    if (m.media === 'photo') {
        return '<img src="/api/tg/media?chat_id=' + chatId + '&msg_id=' + msgId + '&thumb=1" style="max-width:100%;border-radius:6px;margin:4px 0;cursor:pointer;max-height:250px;object-fit:cover;" onclick="window.open(\'/api/tg/media?chat_id=' + chatId + '&msg_id=' + msgId + '\',\'_blank\')" loading="lazy">';
    }
    if (m.media === 'video' || m.media === 'gif' || m.media === 'video_note') {
        var isRound = m.media === 'video_note';
        var roundStyle = isRound ? 'border-radius:50%;width:200px;height:200px;object-fit:cover;' : 'max-width:100%;border-radius:6px;max-height:300px;';
        return '<video src="/api/tg/media?chat_id=' + chatId + '&msg_id=' + msgId + '" style="' + roundStyle + 'margin:4px 0;" controls ' + (m.media === 'gif' ? 'autoplay loop muted' : '') + '></video>';
    }
    if (m.media === 'sticker') {
        return '<img src="/api/tg/media?chat_id=' + chatId + '&msg_id=' + msgId + '" style="max-width:120px;max-height:120px;margin:4px 0;" loading="lazy">';
    }
    if (m.media === 'voice') {
        return '<audio src="/api/tg/media?chat_id=' + chatId + '&msg_id=' + msgId + '" controls style="width:100%;max-width:250px;height:32px;margin:4px 0;"></audio>';
    }
    if (m.media === 'audio') {
        return '<div style="margin:4px 0;">' +
            '<div style="font-size:0.7rem;color:var(--text-muted);margin-bottom:2px;">' + escHtml(m.media_name || 'Аудио') + '</div>' +
            '<audio src="/api/tg/media?chat_id=' + chatId + '&msg_id=' + msgId + '" controls style="width:100%;max-width:280px;height:32px;"></audio>' +
        '</div>';
    }
    if (m.media === 'document') {
        return '<div style="background:var(--surface-info-bg);border-radius:6px;padding:8px;margin:4px 0;display:flex;align-items:center;gap:6px;cursor:pointer;" onclick="window.open(\'/api/tg/media?chat_id=' + chatId + '&msg_id=' + msgId + '\',\'_blank\')">' +
            '<i class="bi bi-file-earmark" style="color:var(--warning);font-size:1.1rem;"></i>' +
            '<span style="font-size:0.72rem;color:var(--accent-light);">' + escHtml(m.media_name || 'Файл') + '</span>' +
        '</div>';
    }
    return '<span style="font-size:0.7rem;color:var(--text-muted);font-style:italic;">[' + m.media + ']</span>';
}

function loadTgMessages(chatId, chatName, chatType) {
    _tgCurrentChat = chatId;
    var typeLabel = chatType === 'channel' ? 'канал' : chatType === 'group' ? 'группа' : '';
    _tgSetHeader(chatName || 'Чат', typeLabel, true);

    var el = document.getElementById('tg-chats');
    if (el) el.innerHTML = '<div style="text-align:center;padding:32px;color:var(--text-muted);"><i class="bi bi-arrow-repeat" style="animation:spin 1s linear infinite;display:inline-block;font-size:1.2rem;"></i></div>';

    API.get('/api/tg/messages?chat_id=' + chatId).then(function(d) {
        if (_tgCurrentChat !== chatId) return;
        if (!el) return;
        if (!d.messages || !d.messages.length) {
            el.innerHTML = '<div style="text-align:center;padding:32px;color:var(--text-muted);">Нет сообщений</div>';
            return;
        }
        var msgs = d.messages.reverse();
        // Reply/Edit state
        var replyHtml = '<div id="tg-reply-bar" style="display:none;padding:6px 12px;background:var(--bg-card-hover);border-top:1px solid #252540;font-size:0.72rem;color:var(--accent-light);align-items:center;gap:8px;">' +
            '<i class="bi bi-reply"></i> <span id="tg-reply-text"></span>' +
            '<span style="margin-left:auto;cursor:pointer;color:var(--text-muted);" onclick="cancelTgReply()"><i class="bi bi-x"></i></span>' +
        '</div>';

        // File attach button + input bar
        var inputHtml = replyHtml +
            '<div id="tg-input-bar" style="padding:8px;border-top:1px solid var(--border);display:flex;gap:6px;align-items:flex-end;background:var(--bg-input);">' +
            '<label style="cursor:pointer;display:flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:10px;background:var(--bg-input);border:1px solid var(--border);flex-shrink:0;" title="Прикрепить файл">' +
                '<i class="bi bi-paperclip" style="color:var(--text-muted);font-size:1rem;"></i>' +
                '<input type="file" id="tg-file-input" style="display:none;" onchange="sendTgFile()">' +
            '</label>' +
            '<textarea id="tg-msg-input" rows="1" placeholder="Написать сообщение..." style="flex:1;resize:none;background:var(--bg-input);border:1px solid var(--border);border-radius:10px;padding:8px 12px;color:var(--text-primary);font-size:0.8rem;font-family:inherit;outline:none;max-height:80px;min-height:34px;" onkeydown="if(event.key===\'Enter\'&&!event.shiftKey){event.preventDefault();sendTgMsg()}"></textarea>' +
            '<button onclick="sendTgMsg()" style="background:var(--accent);border:none;border-radius:10px;width:36px;height:36px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;"><i class="bi bi-send-fill" style="color:#fff;font-size:0.85rem;"></i></button>' +
        '</div>';

        el.innerHTML = '<div style="padding:8px;flex:1;overflow-y:auto;" id="tg-msgs-scroll">' + msgs.map(function(m) {
            var isOut = m.out;
            var bubbleBg = isOut ? '#1a1040' : '#161623';
            var bubbleAlign = isOut ? 'margin-left:15%;' : 'margin-right:15%;';
            var senderColor = isOut ? 'var(--accent-light)' : '#3b82f6';
            var mediaHtml = _tgRenderMedia(m);
            var textHtml = m.text ? '<div style="font-size:0.78rem;word-break:break-word;line-height:1.45;">' + escHtml(m.text) + '</div>' : '';
            var replyPreview = m.reply_text ? '<div style="border-left:2px solid var(--accent);padding:2px 6px;margin-bottom:4px;font-size:0.65rem;color:var(--text-muted);"><b>' + escHtml(m.reply_sender || '') + '</b> ' + escHtml(m.reply_text).substring(0, 60) + '</div>' : '';
            var avatarUrl = '/api/tg/avatar?chat_id=' + (m.sender_id || m.chat_id);

            // Context menu data
            var dataAttrs = 'data-mid="' + m.id + '" data-cid="' + chatId + '" data-out="' + (isOut?1:0) + '" data-text="' + escHtml(m.text || '').replace(/"/g,'&quot;') + '"';

            return '<div class="tg-bubble" ' + dataAttrs + ' oncontextmenu="tgMsgMenu(event,this)" style="' + bubbleAlign + 'background:' + bubbleBg + ';border-radius:10px;padding:6px 10px;margin-bottom:4px;max-width:85%;position:relative;cursor:default;">' +
                '<div style="display:flex;gap:6px;align-items:flex-start;">' +
                (isOut ? '' : '<img src="' + avatarUrl + '" style="width:28px;height:28px;border-radius:50%;object-fit:cover;flex-shrink:0;margin-top:2px;" onerror="this.style.display=\'none\'">') +
                '<div style="flex:1;min-width:0;">' +
                (isOut ? '' : '<div style="font-size:0.62rem;color:' + senderColor + ';font-weight:600;margin-bottom:2px;">' + escHtml(m.sender || '') + '</div>') +
                replyPreview +
                mediaHtml +
                textHtml +
                '<div style="text-align:right;margin-top:2px;display:flex;justify-content:flex-end;align-items:center;gap:4px;">' +
                    (m.edited ? '<span style="font-size:0.5rem;color:#4a5568;">ред.</span>' : '') +
                    '<span style="font-size:0.55rem;color:#4a5568;">' + (m.time || '') + '</span>' +
                '</div>' +
                '</div></div>' +
            '</div>';
        }).join('') + '</div>' + inputHtml;

        // Mark as read
        API.post('/api/tg/read', {chat_id: chatId}).catch(function(){});

        var scroll = document.getElementById('tg-msgs-scroll');
        if (scroll) scroll.scrollTop = scroll.scrollHeight;
    }).catch(function() {
        if (el) el.innerHTML = '<div style="text-align:center;padding:32px;color:var(--danger);">Ошибка загрузки</div>';
    });
}

var _tgReplyTo = null;
var _tgEditId = null;

function tgMsgMenu(e, el) {
    e.preventDefault();
    var old = document.getElementById('tg-ctx-menu');
    if (old) old.remove();
    var mid = el.dataset.mid;
    var cid = el.dataset.cid;
    var isOut = el.dataset.out === '1';
    var text = el.dataset.text;

    var menu = document.createElement('div');
    menu.id = 'tg-ctx-menu';
    menu.style.cssText = 'position:fixed;z-index:9999;background:var(--bg-input);border:1px solid var(--border);border-radius:10px;padding:4px 0;min-width:160px;box-shadow:0 8px 24px rgba(0,0,0,0.4);';
    menu.style.left = Math.min(e.clientX, window.innerWidth - 180) + 'px';
    menu.style.top = Math.min(e.clientY, window.innerHeight - 200) + 'px';

    function item(icon, label, fn) {
        var d = document.createElement('div');
        d.style.cssText = 'padding:8px 14px;font-size:0.78rem;cursor:pointer;display:flex;align-items:center;gap:8px;color:var(--text-secondary);';
        d.innerHTML = '<i class="bi ' + icon + '" style="font-size:0.85rem;width:16px;"></i>' + label;
        d.onmouseover = function() { d.style.background = '#1a1a2e'; };
        d.onmouseout = function() { d.style.background = 'none'; };
        d.onclick = function() { menu.remove(); fn(); };
        menu.appendChild(d);
    }

    item('bi-reply', 'Ответить', function() { setTgReply(mid, text); });
    if (isOut && text) item('bi-pencil', 'Редактировать', function() { setTgEdit(mid, cid, text); });
    item('bi-forward', 'Переслать', function() { tgForwardMsg(mid, cid); });
    if (isOut) item('bi-trash', 'Удалить', function() { tgDeleteMsg(mid, cid); });

    document.body.appendChild(menu);
    setTimeout(function() {
        document.addEventListener('click', function _close() { menu.remove(); document.removeEventListener('click', _close); }, {once: true});
    }, 50);
}

function setTgReply(mid, text) {
    _tgReplyTo = mid;
    _tgEditId = null;
    var bar = document.getElementById('tg-reply-bar');
    var rt = document.getElementById('tg-reply-text');
    if (bar) { bar.style.display = 'flex'; }
    if (rt) rt.textContent = 'Ответ: ' + (text || '').substring(0, 50);
    var input = document.getElementById('tg-msg-input');
    if (input) input.focus();
}

function setTgEdit(mid, cid, text) {
    _tgEditId = mid;
    _tgReplyTo = null;
    var bar = document.getElementById('tg-reply-bar');
    var rt = document.getElementById('tg-reply-text');
    if (bar) { bar.style.display = 'flex'; }
    if (rt) rt.textContent = 'Редактирование';
    var input = document.getElementById('tg-msg-input');
    if (input) { input.value = text.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"'); input.focus(); }
}

function cancelTgReply() {
    _tgReplyTo = null;
    _tgEditId = null;
    var bar = document.getElementById('tg-reply-bar');
    if (bar) bar.style.display = 'none';
    var input = document.getElementById('tg-msg-input');
    if (input) input.value = '';
}

function tgDeleteMsg(mid, cid) {
    if (!confirm('Удалить сообщение у обоих?')) return;
    API.post('/api/tg/delete', {chat_id: cid, msg_ids: [mid]}).then(function(d) {
        if (d.ok) loadTgMessages(_tgCurrentChat, '', '');
        else showToast(d.error || 'Ошибка', 'error');
    });
}

function tgForwardMsg(mid, cid) {
    var to = prompt('ID чата для пересылки (или @username):');
    if (!to) return;
    API.post('/api/tg/forward', {from_chat: cid, to_chat: to, msg_ids: [mid]}).then(function(d) {
        if (d.ok) showToast('Переслано', 'success');
        else showToast(d.error || 'Ошибка', 'error');
    });
}

function sendTgFile() {
    var fileInput = document.getElementById('tg-file-input');
    if (!fileInput || !fileInput.files.length || !_tgCurrentChat) return;
    var fd = new FormData();
    fd.append('file', fileInput.files[0]);
    fd.append('chat_id', _tgCurrentChat);
    fd.append('caption', '');
    fetch('/api/tg/send_file', {method: 'POST', body: fd}).then(function(r) { return r.json(); }).then(function(d) {
        if (d.ok) loadTgMessages(_tgCurrentChat, '', '');
        else showToast(d.error || 'Ошибка', 'error');
    }).catch(function() { showToast('Ошибка загрузки', 'error'); });
    fileInput.value = '';
}

function sendTgMsg() {
    var input = document.getElementById('tg-msg-input');
    if (!input || !_tgCurrentChat) return;
    var text = input.value.trim();
    if (!text) return;
    input.disabled = true;

    var promise;
    if (_tgEditId) {
        promise = API.post('/api/tg/edit', {chat_id: _tgCurrentChat, msg_id: _tgEditId, text: text});
    } else {
        var payload = {chat_id: _tgCurrentChat, text: text};
        if (_tgReplyTo) payload.reply_to = _tgReplyTo;
        promise = API.post('/api/tg/send', payload);
    }

    promise.then(function(d) {
        input.disabled = false;
        if (d.ok) {
            input.value = '';
            cancelTgReply();
            loadTgMessages(_tgCurrentChat, '', '');
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    }).catch(function(e) {
        input.disabled = false;
        showToast(e.error || 'Ошибка', 'error');
    });
}

function loadTgReplies() {
    API.get('/api/tg/incoming').then(function(d) {
        var el = document.getElementById('tg-replies');
        if (!el) return;
        if (!d.replies || !d.replies.length) {
            el.innerHTML = '<div style="text-align:center;color:#64748b;padding:32px;"><i class="bi bi-chat-dots" style="font-size:1.5rem;display:block;margin-bottom:8px;"></i>Нет входящих</div>';
            return;
        }
        el.innerHTML = d.replies.map(function(r) {
            var initial = (r.user || '?').charAt(0).toUpperCase();
            return '<div onclick="loadTgMessages(' + r.user_id + ',\'' + escHtml(r.user).replace(/'/g,"\\'") + '\',\'user\')" style="padding:10px 12px;border-bottom:1px solid #1a1a2e;cursor:pointer;transition:background 0.15s;" onmouseover="this.style.background=\'#161028\'" onmouseout="this.style.background=\'none\'">' +
                '<div style="display:flex;align-items:center;gap:10px;">' +
                    '<div style="width:36px;height:36px;border-radius:50%;background:#3b82f6;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:700;font-size:0.8rem;color:#fff;">' + initial + '</div>' +
                    '<div style="flex:1;min-width:0;">' +
                        '<div style="display:flex;justify-content:space-between;align-items:center;">' +
                            '<span style="font-size:0.8rem;font-weight:600;">' + escHtml(r.user) + '</span>' +
                            '<span style="background:var(--accent);color:#fff;border-radius:10px;padding:2px 7px;font-size:0.6rem;font-weight:700;">' + r.unread + '</span>' +
                        '</div>' +
                        '<div style="font-size:0.72rem;color:#94a3b8;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' + escHtml(r.text) + '</div>' +
                    '</div>' +
                '</div>' +
            '</div>';
        }).join('');
    }).catch(function() {});
}

// ── Warnings — proxy & timings ──────────────────────────────────

function checkWarnings() {
    API.get('/api/proxy/list').then(function(p) {
        var proxies = p.proxies || [];
        var total = proxies.length;
        var alive = proxies.filter(function(x){ return x.alive === true; }).length;
        var unchecked = proxies.filter(function(x){ return x.alive === null || x.alive === undefined || x.status === 'unchecked'; }).length;
        if (total === 0) {
            if (typeof addLog === 'function') addLog('Прокси не добавлен. Без прокси аккаунт может быть заблокирован!', 'WARNING');
        } else if (alive === 0 && unchecked === 0) {
            // Показываем только если все прокси УЖЕ проверены и все мёртвые.
            // Раньше ругались сразу при старте на непроверенные — клиент терялся.
            if (typeof addLog === 'function') addLog('Все прокси мёртвые! Нажмите «Проверить все» в настройках', 'WARNING');
        }
    }).catch(function(){});
    // Проверяем текущие тайминги + состояние файла настроек.
    // Warning только если клиент ТОЧНО ещё не настраивал (все дефолты + файл не пишется).
    API.get('/api/settings').then(function(s) {
        if (!s.spam) return;
        // Считаем кастомизацию сразу по набору всех основных полей — если хоть
        // одно отличается от дефолта, клиент уже что-то менял, предупреждение не нужно.
        var usingDefaults = (s.spam.min_delay === 6 && s.spam.max_delay === 14 &&
                              s.spam.burst_size === 7);
        if (!usingDefaults) return;
        API.get('/api/settings/debug').then(function(dbg) {
            if (dbg.settings_exists && (dbg.settings_keys || []).length === 0) {
                if (typeof addLog === 'function') addLog('Файл настроек существует но пуст! Тайминги сброшены к 6-14с. Открой Настройки → задай значения → автосохранение. Путь: ' + dbg.settings_path, 'ERROR');
            } else if (!dbg.settings_exists) {
                if (typeof addLog === 'function') addLog('Тайминги рассылки дефолтные 6-14с (файл настроек ещё не создан). Задай свои в Настройках — сохранится автоматически.', 'WARNING');
            } else {
                if (typeof addLog === 'function') addLog('Тайминги рассылки стандартные (6-14с). Для безопасности рекомендуется увеличить в Настройках', 'WARNING');
            }
        }).catch(function() {
            if (typeof addLog === 'function') addLog('Тайминги рассылки стандартные (6-14с). Для безопасности рекомендуется увеличить в Настройках', 'WARNING');
        });
    }).catch(function(){});
}

// ── Poll logs from server ───────────────────────────────────────

var _lastLogId = 0;
function pollLogs() {
    API.get('/api/logs/recent?since=' + _lastLogId).then(function(d) {
        if (d.logs) {
            d.logs.forEach(function(l) {
                if (l.id > _lastLogId) _lastLogId = l.id;
                if (typeof addLog === 'function') addLog(l.message, l.level, l.module || 'system');
            });
        }
    }).catch(function() {});
}
setInterval(pollLogs, 2000);

// ── Onboarding (первый запуск) ─────────────────────────────────

var _onboardingStep = 0;
var _onboardingSteps = [];

function checkOnboarding(accountsCount) {
    // Показываем только если 0 аккаунтов И клиент ещё ни разу не видел онбординг.
    // Флаг сохраняется на сервере (файл data/onboarding_done.flag) чтобы не зависеть
    // от webview localStorage, который может чиститься private_mode при обновлении.
    if (accountsCount > 0) return;
    if (localStorage.getItem('onboarding_done')) return;
    if (window._serverOnboardingDone) return;
    localStorage.setItem('onboarding_done', '1');
    try { API.post('/api/onboarding/mark_done', {}); } catch (e) {}

    var keyType = _keyType || 'pro';
    var typeLabel = {trial: 'Trial (пробный)', standard: 'Standard', pro: 'Pro', custom: 'Custom'}[keyType] || keyType;

    _onboardingSteps = [
        // Шаг 1: Приветствие
        {
            html: '<div style="text-align:center;">' +
                '<div style="font-size:2.5rem;margin-bottom:12px;">&#128075;</div>' +
                '<h2 style="color:#fff;font-size:1.3rem;margin-bottom:8px;">Добро пожаловать в TelePush!</h2>' +
                '<p style="color:#9ca3af;font-size:0.85rem;margin-bottom:4px;">Ваш тариф: <b style="color:var(--accent-light);">' + typeLabel + '</b></p>' +
                '<p style="color:#6b7280;font-size:0.78rem;">Давайте настроим софт за пару минут</p>' +
            '</div>'
        },
        // Шаг 2: Добавление аккаунта
        {
            html: '<div>' +
                '<h2 style="color:#fff;font-size:1.1rem;margin-bottom:12px;">&#128100; Шаг 1: Добавьте аккаунт</h2>' +
                '<p style="color:#9ca3af;font-size:0.82rem;margin-bottom:16px;">Есть три способа:</p>' +
                '<div style="display:flex;flex-direction:column;gap:10px;">' +
                    '<div style="background:var(--bg-card-hover);border:1px solid var(--border);border-radius:10px;padding:12px;">' +
                        '<b style="color:#a78bfa;font-size:0.82rem;">Способ 1: Файлы .session + .json</b>' +
                        '<p style="color:#6b7280;font-size:0.75rem;margin-top:4px;">Если у вас есть готовые файлы сессии — перетащите их в зону загрузки на вкладке Аккаунты</p>' +
                    '</div>' +
                    '<div style="background:var(--bg-card-hover);border:1px solid var(--border);border-radius:10px;padding:12px;">' +
                        '<b style="color:#60a5fa;font-size:0.82rem;">Способ 2: По номеру телефона</b>' +
                        '<p style="color:#6b7280;font-size:0.75rem;margin-top:4px;">Введите номер → получите код в Telegram → введите код. Аккаунт добавится автоматически</p>' +
                    '</div>' +
                    '<div style="background:var(--bg-card-hover);border:1px solid var(--border);border-radius:10px;padding:12px;">' +
                        '<b style="color:#4ade80;font-size:0.82rem;">Способ 3: Из папки</b>' +
                        '<p style="color:#6b7280;font-size:0.75rem;margin-top:4px;">Закиньте файлы в папку Аккаунты/В работе/ рядом с программой и нажмите «Обновить из папки»</p>' +
                    '</div>' +
                '</div>' +
            '</div>'
        },
        // Шаг 3: Что можно делать
        {
            html: '<div>' +
                '<h2 style="color:#fff;font-size:1.1rem;margin-bottom:12px;">&#128640; Шаг 2: Выберите задачу</h2>' +
                '<p style="color:#9ca3af;font-size:0.82rem;margin-bottom:12px;">После добавления аккаунта вам доступны:</p>' +
                '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">' +
                    (hasModule('spam') ? '<div style="background:var(--bg-card-hover);border-radius:8px;padding:10px;"><b style="color:#a78bfa;font-size:0.78rem;">Рассылка</b><p style="color:#6b7280;font-size:0.7rem;margin-top:2px;">Сообщения в группы и каналы</p></div>' : '') +
                    (hasModule('dm') ? '<div style="background:var(--bg-card-hover);border-radius:8px;padding:10px;"><b style="color:#818cf8;font-size:0.78rem;">Личные сообщения</b><p style="color:#6b7280;font-size:0.7rem;margin-top:2px;">Рассылка в ЛС по юзернеймам</p></div>' : '') +
                    (hasModule('join') ? '<div style="background:var(--bg-card-hover);border-radius:8px;padding:10px;"><b style="color:#60a5fa;font-size:0.78rem;">Подписка</b><p style="color:#6b7280;font-size:0.7rem;margin-top:2px;">Вступление в группы по ссылкам</p></div>' : '') +
                    (hasModule('parser') ? '<div style="background:var(--bg-card-hover);border-radius:8px;padding:10px;"><b style="color:#f472b6;font-size:0.78rem;">Парсинг</b><p style="color:#6b7280;font-size:0.7rem;margin-top:2px;">Сбор юзернеймов из чатов</p></div>' : '') +
                    (hasModule('warmup') ? '<div style="background:var(--bg-card-hover);border-radius:8px;padding:10px;"><b style="color:#fb923c;font-size:0.78rem;">Прогрев</b><p style="color:#6b7280;font-size:0.7rem;margin-top:2px;">Подготовка новых аккаунтов</p></div>' : '') +
                    (hasModule('multi') ? '<div style="background:var(--bg-card-hover);border-radius:8px;padding:10px;"><b style="color:#c084fc;font-size:0.78rem;">Мульти-аккаунт</b><p style="color:#6b7280;font-size:0.7rem;margin-top:2px;">Несколько аккаунтов параллельно</p></div>' : '') +
                '</div>' +
            '</div>'
        },
        // Шаг 4: Прокси
        {
            html: '<div>' +
                '<h2 style="color:#fff;font-size:1.1rem;margin-bottom:12px;">&#128274; Шаг 3: Прокси (рекомендуется)</h2>' +
                '<p style="color:#9ca3af;font-size:0.82rem;margin-bottom:12px;">Для безопасности аккаунтов рекомендуем добавить прокси.</p>' +
                '<div style="background:var(--surface-warning-bg);border:1px solid var(--surface-warning-border);border-radius:10px;padding:12px;margin-bottom:12px;">' +
                    '<p style="color:#fbbf24;font-size:0.78rem;">Без прокси аккаунты могут быть заблокированы Telegram. Особенно новые.</p>' +
                '</div>' +
                '<p style="color:#6b7280;font-size:0.78rem;">Перейдите на вкладку <b style="color:#a78bfa;">Прокси</b> в меню слева и добавьте свои прокси. Софт автоматически определит тип (SOCKS5/HTTP) и распределит между аккаунтами.</p>' +
            '</div>'
        },
        // Шаг 5: Готово
        {
            html: '<div style="text-align:center;">' +
                '<div style="font-size:2.5rem;margin-bottom:12px;">&#9989;</div>' +
                '<h2 style="color:#fff;font-size:1.3rem;margin-bottom:8px;">Всё готово!</h2>' +
                '<p style="color:#9ca3af;font-size:0.85rem;margin-bottom:16px;">Добавьте аккаунт и начинайте работать</p>' +
                '<div style="display:flex;gap:8px;justify-content:center;">' +
                    '<button class="btn btn-accent btn-sm" onclick="closeOnboarding();showTab(\'accounts\')"><i class="bi bi-person-plus"></i> Добавить аккаунт</button>' +
                    '<button class="btn btn-outline btn-sm" onclick="closeOnboarding();showTab(\'guide\')"><i class="bi bi-book"></i> Инструкция</button>' +
                '</div>' +
            '</div>',
            noNext: true
        },
    ];

    _onboardingStep = 0;
    renderOnboarding();
    document.getElementById('onboarding-overlay').classList.remove('hidden');
}

function renderOnboarding() {
    var step = _onboardingSteps[_onboardingStep];
    var el = document.getElementById('onboarding-content');
    if (!el || !step) return;

    var nav = '<div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px;padding-top:16px;border-top:1px solid #252540;">' +
        '<div style="font-size:0.72rem;color:#4b5060;">' + (_onboardingStep + 1) + ' / ' + _onboardingSteps.length + '</div>' +
        '<div style="display:flex;gap:8px;">';

    if (_onboardingStep > 0) {
        nav += '<button class="btn btn-outline btn-sm" onclick="onboardingPrev()" style="font-size:0.75rem;padding:4px 14px;">Назад</button>';
    }
    nav += '<button class="btn btn-outline btn-sm" onclick="closeOnboarding()" style="font-size:0.75rem;padding:4px 14px;color:var(--text-muted);">Пропустить</button>';
    if (!step.noNext) {
        nav += '<button class="btn btn-accent btn-sm" onclick="onboardingNext()" style="font-size:0.75rem;padding:4px 14px;">Далее</button>';
    }
    nav += '</div></div>';

    el.innerHTML = step.html + nav;
}

function onboardingNext() {
    if (_onboardingStep < _onboardingSteps.length - 1) {
        _onboardingStep++;
        renderOnboarding();
    }
}

function onboardingPrev() {
    if (_onboardingStep > 0) {
        _onboardingStep--;
        renderOnboarding();
    }
}

function closeOnboarding() {
    document.getElementById('onboarding-overlay').classList.add('hidden');
}

// ── Module Access Control ──────────────────────────────────────

var _allowedModules = [];
var _allowedFeatures = {};
var _keyType = 'pro';

function hasModule(name) { return !_allowedModules.length || _allowedModules.indexOf(name) !== -1; }
function hasFeature(name) { return _allowedFeatures[name] !== false; }

function _applyModuleLocks() {
    if (!_allowedModules.length) return;  // no restrictions

    var alwaysOpen = ['settings', 'guide', 'profile', 'support'];
    document.querySelectorAll('.nav-link[data-tab]').forEach(function(link) {
        var tab = link.getAttribute('data-tab');
        if (alwaysOpen.indexOf(tab) !== -1) return;

        // Map tab names to module names
        var moduleMap = {
            'spam': 'spam', 'dm': 'dm', 'join': 'join', 'multi': 'multi',
            'accounts': 'accounts', 'proxy': 'proxy', 'parser': 'parser',
            'warmup': 'warmup', 'profiles': 'profiles', 'autoreply': 'autoreply',
            'templates': 'templates', 'analytics': 'analytics', 'reauth': 'reauth',
        };
        var module = moduleMap[tab];
        if (!module) return;

        var allowed = _allowedModules.indexOf(module) !== -1;
        if (!allowed) {
            link.style.opacity = '0.35';
            link.style.pointerEvents = 'none';
            if (!link.querySelector('.lock-icon')) {
                var lock = document.createElement('i');
                lock.className = 'bi bi-lock-fill lock-icon';
                lock.style.cssText = 'color:#f87171;font-size:0.65rem;margin-left:auto;';
                link.appendChild(lock);
            }
        } else {
            link.style.opacity = '';
            link.style.pointerEvents = '';
            var lockIcon = link.querySelector('.lock-icon');
            if (lockIcon) lockIcon.remove();
        }
    });
    _applyFeatureLocks();
}

function _applyFeatureLocks() {
    document.querySelectorAll('[data-feature]').forEach(function(el) {
        var feature = el.getAttribute('data-feature');
        var allowed = hasFeature(feature);
        var existing = el.querySelector(':scope > .feature-blocker');
        if (!allowed) {
            if (existing) return;
            if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
            var ov = document.createElement('div');
            ov.className = 'feature-blocker';
            ov.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(7,7,13,0.78);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);z-index:50;display:flex;flex-direction:column;align-items:center;justify-content:center;border-radius:10px;cursor:default;text-align:center;padding:12px;';
            ov.innerHTML =
                '<i class="bi bi-lock-fill" style="font-size:1.4rem;color:#f87171;margin-bottom:6px;"></i>' +
                '<div style="font-size:0.78rem;font-weight:600;color:#fff;margin-bottom:4px;">Функция недоступна</div>' +
                '<div style="font-size:0.68rem;color:#9ca3af;margin-bottom:8px;">Доступно в тарифе Pro</div>' +
                '<button class="btn btn-accent btn-xs" style="font-size:0.7rem;padding:4px 10px;" onclick="requestFeatureAccess(\'' + feature + '\')"><i class="bi bi-key"></i> Запросить</button>';
            ov.addEventListener('click', function(e){ e.stopPropagation(); });
            el.appendChild(ov);
        } else if (existing) {
            existing.remove();
        }
    });
}

function requestFeatureAccess(featureName) {
    API.post('/api/ticket', {
        module: featureName,
        subject: 'Запрос доступа к функции: ' + featureName,
        body: 'Хочу получить доступ к функции ' + featureName
    }).then(function() {
        showToast('Запрос отправлен!', 'success');
    }).catch(function() { showToast('Ошибка', 'error'); });
}

function _showModuleBlocked(tabName) {
    var sec = document.getElementById('tab-' + tabName);
    if (!sec) return;
    // Check if already has blocker
    if (sec.querySelector('.module-blocker')) return;
    var blocker = document.createElement('div');
    blocker.className = 'module-blocker';
    blocker.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(7,7,13,0.85);backdrop-filter:blur(8px);z-index:100;display:flex;flex-direction:column;align-items:center;justify-content:center;border-radius:var(--radius);';
    blocker.innerHTML =
        '<i class="bi bi-lock-fill" style="font-size:3rem;color:#f87171;margin-bottom:16px;"></i>' +
        '<div style="font-size:1.1rem;font-weight:700;color:#fff;margin-bottom:8px;">Модуль недоступен</div>' +
        '<div style="font-size:0.82rem;color:#9ca3af;margin-bottom:20px;text-align:center;max-width:300px;">Этот модуль не входит в ваш тарифный план. Запросите доступ или обновите тариф.</div>' +
        '<button class="btn btn-accent btn-sm" onclick="requestModuleAccess(\'' + tabName + '\')"><i class="bi bi-key"></i> Запросить доступ</button>';
    sec.style.position = 'relative';
    sec.appendChild(blocker);
}

function requestModuleAccess(moduleName) {
    API.post('/api/ticket', {module: moduleName, subject: 'Запрос доступа к модулю: ' + moduleName, body: 'Хочу получить доступ к модулю ' + moduleName}).then(function() {
        showToast('Запрос отправлен!', 'success');
    }).catch(function() { showToast('Ошибка', 'error'); });
}

// ── Expired Lock ───────────────────────────────────────────────

var _expiredLocked = false;
function _applyExpiredLock(locked) {
    if (_expiredLocked === locked) return;
    _expiredLocked = locked;
    // Lock sidebar nav items (except profile, settings, support)
    var allowedTabs = ['profile', 'settings', 'support'];
    document.querySelectorAll('.nav-link[data-tab]').forEach(function(link) {
        var tab = link.getAttribute('data-tab');
        if (allowedTabs.indexOf(tab) !== -1) return;
        if (locked) {
            link.style.opacity = '0.4';
            link.style.pointerEvents = 'none';
            // Add lock icon if not already
            if (!link.querySelector('.lock-icon')) {
                var lock = document.createElement('i');
                lock.className = 'bi bi-lock-fill lock-icon';
                lock.style.cssText = 'color:var(--danger);font-size:0.7rem;margin-left:4px;';
                link.appendChild(lock);
            }
        } else {
            link.style.opacity = '';
            link.style.pointerEvents = '';
            var lockIcon = link.querySelector('.lock-icon');
            if (lockIcon) lockIcon.remove();
        }
    });
    // Disable all start buttons
    if (locked) {
        document.querySelectorAll('button[onclick*="Start"], button[onclick*="start"], button[onclick*="Spam"], button[onclick*="Join"], button[onclick*="multi"]').forEach(function(btn) {
            btn.disabled = true;
            btn.title = 'Лицензия истекла';
        });
    }
}

function requestRenewal() {
    var tgUser = (document.getElementById('renew-tg-username') || {}).value || '';
    tgUser = tgUser.trim().replace(/^@/, '');
    if (!tgUser) {
        showToast('Введите ваш @username в Telegram', 'warning');
        var inp = document.getElementById('renew-tg-username');
        if (inp) { inp.focus(); inp.style.borderColor = 'var(--danger)'; }
        return;
    }
    var btn = document.getElementById('btn-renew');
    if (btn) { btn.disabled = true; btn.innerHTML = '<i class="bi bi-hourglass-split"></i> Отправка...'; }
    API.post('/api/renew', {tg_username: tgUser}).then(function(d) {
        var res = document.getElementById('renew-result');
        if (res) {
            res.style.display = 'block';
            if (d.ok) {
                res.style.color = 'var(--success)';
                res.textContent = 'Запрос отправлен! Ожидайте — администратор продлит ключ.';
            } else {
                res.style.color = 'var(--danger)';
                res.textContent = d.error || 'Ошибка';
                if (btn) { btn.disabled = false; btn.innerHTML = '<i class="bi bi-key"></i> Запросить продление'; }
            }
        }
    }).catch(function() {
        var res = document.getElementById('renew-result');
        if (res) { res.style.display = 'block'; res.style.color = 'var(--danger)'; res.textContent = 'Нет связи с сервером'; }
        if (btn) { btn.disabled = false; btn.innerHTML = '<i class="bi bi-key"></i> Запросить продление'; }
    });
}

// ── Profile ────────────────────────────────────────────────────

function loadProfile() {
    API.get('/api/profile').then(function(d) {
        var el;
        el = document.getElementById('prf-key'); if (el) el.textContent = d.key || '---';
        el = document.getElementById('prf-expires');
        if (el) {
            if (d.expires) {
                var exp = new Date(d.expires);
                var dd = String(exp.getDate()).padStart(2,'0');
                var mm = String(exp.getMonth()+1).padStart(2,'0');
                var yy = exp.getFullYear();
                var nice = dd + '.' + mm + '.' + yy;
                if (exp < new Date()) { el.style.color = 'var(--danger)'; el.textContent = nice + ' (истёк)'; }
                else {
                    var days = Math.ceil((exp - new Date()) / 86400000);
                    el.style.color = days < 7 ? 'var(--warning)' : 'var(--success)';
                    el.textContent = nice + ' (' + days + ' дн.)';
                }
            } else { el.textContent = 'Бессрочная'; el.style.color = 'var(--success)'; }
        }
        el = document.getElementById('prf-version'); if (el) el.textContent = 'v' + (d.version || '?');
        el = document.getElementById('prf-key-type');
        if (el) {
            var typeLabels = {trial: 'Trial (7 дней)', standard: 'Standard', pro: 'Pro', custom: 'Custom'};
            var kt = _keyType || 'pro';
            el.textContent = typeLabels[kt] || kt;
            el.style.color = kt === 'pro' ? 'var(--success)' : kt === 'trial' ? 'var(--warning)' : 'var(--accent-light)';
        }
        el = document.getElementById('prf-hwid'); if (el) el.textContent = d.hwid || '---';
        el = document.getElementById('prf-pc'); if (el) el.textContent = d.pc_name || '---';
        el = document.getElementById('prf-accs');
        if (el) el.innerHTML = '<span style="color:var(--success);">' + (d.accounts_alive || 0) + ' живых</span>' +
            (d.accounts_busy ? ' / <span style="color:var(--accent);">' + d.accounts_busy + ' в работе</span>' : '') +
            ' / ' + (d.accounts_total || 0) + ' всего';
        el = document.getElementById('prf-site-link');
        if (el) el.href = (d.site_url || 'https://telepush.org') + '/profile';
        el = document.getElementById('prf-modules');
        if (el) {
            var moduleLabels = {
                spam: 'Рассылка', dm: 'Личные', join: 'Подписка', multi: 'Мульти',
                accounts: 'Аккаунты', proxy: 'Прокси', parser: 'Парсинг',
                warmup: 'Прогрев', profiles: 'Профили', autoreply: 'Автоответ',
                templates: 'Шаблоны', analytics: 'Аналитика', tg_messenger: 'TG мессенджер'
            };
            var alwaysOpen = {settings: 'Настройки', profile: 'Профиль', support: 'Поддержка', guide: 'Инструкция'};
            var allowed = (d.modules && d.modules.length) ? d.modules : Object.keys(moduleLabels);
            var html = '';
            Object.keys(moduleLabels).forEach(function(m) {
                var on = allowed.indexOf(m) !== -1;
                var bg = on ? 'background:rgba(34,197,94,0.12);border:1px solid rgba(34,197,94,0.35);color:#86efac;' : 'background:rgba(248,113,113,0.08);border:1px solid rgba(248,113,113,0.25);color:#fca5a5;opacity:0.55;';
                var ic = on ? 'bi-check-circle' : 'bi-lock';
                html += '<span style="display:inline-flex;align-items:center;gap:4px;padding:4px 9px;border-radius:6px;font-size:0.72rem;font-weight:600;' + bg + '"><i class="bi ' + ic + '" style="font-size:0.7rem;"></i>' + moduleLabels[m] + '</span>';
            });
            Object.keys(alwaysOpen).forEach(function(m) {
                html += '<span style="display:inline-flex;align-items:center;gap:4px;padding:4px 9px;border-radius:6px;font-size:0.72rem;font-weight:600;background:rgba(34,197,94,0.12);border:1px solid rgba(34,197,94,0.35);color:#86efac;"><i class="bi bi-check-circle" style="font-size:0.7rem;"></i>' + alwaysOpen[m] + '</span>';
            });
            el.innerHTML = html;
        }
    }).catch(function() {});
}

function resetHwid() {
    if (!confirm('Сбросить привязку к этому ПК? Нужно будет войти заново.')) return;
    var btn = document.getElementById('btn-reset-hwid');
    if (btn) btn.disabled = true;
    API.post('/api/profile/reset_hwid', {}).then(function(d) {
        var res = document.getElementById('reset-result');
        if (res) {
            res.style.display = 'block';
            if (d.ok) {
                res.style.color = 'var(--success)';
                res.textContent = d.msg || 'Готово! Перенаправление...';
                setTimeout(function() { window.location.href = '/login'; }, 1500);
            } else {
                res.style.color = 'var(--danger)';
                res.textContent = d.error || 'Ошибка';
                if (btn) btn.disabled = false;
            }
        }
    }).catch(function() {
        var res = document.getElementById('reset-result');
        if (res) { res.style.display = 'block'; res.style.color = 'var(--danger)'; res.textContent = 'Нет связи с сервером'; }
        if (btn) btn.disabled = false;
    });
}

// ── Live auto-refresh for active tab ────────────────────────────

var _currentTab = 'spam';
var _origShowTab = showTab;
showTab = function(name) {
    _currentTab = name;
    _origShowTab(name);
};

// No live refresh of tabs — prevents scroll reset, avatar flicker, and DOM rebuild
// Data refreshes when user switches tabs via showTab()

// ── Init ────────────────────────────────────────────────────────

document.addEventListener('DOMContentLoaded', function() {
    showTab('spam');
    refreshStatus();
    scheduleRefresh();
    checkUpdate();
    renderAccountSelectors();
    loadTheme();
    setTimeout(checkWarnings, 3000);
});

// ══════════════════════════════════════════════════════════════════
//  PROFILE FILLER
// ══════════════════════════════════════════════════════════════════

function toggleGenName() {
    var checked = document.getElementById('pf-gen-name').checked;
    var input = document.getElementById('pf-firstname');
    input.disabled = checked;
    if (checked) input.placeholder = 'Автогенерация включена';
    else input.placeholder = 'Имя или {Имя1|Имя2}';
}

function toggleGenSurname() {
    var checked = document.getElementById('pf-gen-surname').checked;
    var input = document.getElementById('pf-lastname');
    input.disabled = checked;
    if (checked) input.placeholder = 'Автогенерация включена';
    else input.placeholder = 'Фамилия или {Фам1|Фам2}';
}

function previewGenName() {
    API.get('/api/profile/generate_name').then(function(d) {
        document.getElementById('pf-gen-preview').textContent = d.first_name + ' ' + d.last_name;
    });
}

function toggleGenUsername() {
    var checked = document.getElementById('pf-gen-username').checked;
    var input = document.getElementById('pf-username');
    input.disabled = checked;
    if (checked) input.placeholder = 'Автогенерация включена';
    else input.placeholder = 'username или оставь пусто';
}

function previewGenUsername() {
    API.get('/api/profile/generate_username').then(function(d) {
        document.getElementById('pf-gen-username-preview').textContent = '@' + d.username;
    });
}

function toggleEmojiAvatar() {
    var checked = document.getElementById('pf-avatar-emoji').checked;
    document.getElementById('pf-emoji-row').style.display = checked ? 'block' : 'none';
    if (checked) {
        document.getElementById('pf-avatar-input').value = '';
        document.getElementById('pf-avatar-name').textContent = 'Эмодзи-аватарка';
        document.getElementById('pf-avatar-preview').style.display = 'none';
        // Отключаем папочный режим — эмодзи и папка взаимоисключающие
        var f = document.getElementById('pf-avatar-folder-enabled');
        if (f && f.checked) { f.checked = false; toggleFolderAvatar(); }
    } else {
        document.getElementById('pf-avatar-name').textContent = 'Не выбрано';
    }
}

function toggleFolderAvatar() {
    var checked = document.getElementById('pf-avatar-folder-enabled').checked;
    document.getElementById('pf-folder-row').style.display = checked ? 'block' : 'none';
    if (checked) {
        document.getElementById('pf-avatar-input').value = '';
        document.getElementById('pf-avatar-name').textContent = 'Папка с фото';
        document.getElementById('pf-avatar-preview').style.display = 'none';
        var e = document.getElementById('pf-avatar-emoji');
        if (e && e.checked) { e.checked = false; toggleEmojiAvatar(); }
    } else {
        document.getElementById('pf-avatar-name').textContent = 'Не выбрано';
    }
}

function scanAvatarFolder() {
    var folder = (document.getElementById('pf-avatar-folder') || {}).value || '';
    if (!folder.trim()) {
        showToast('Введи путь к папке', 'warning');
        return;
    }
    API.post('/api/profile/avatar_folder_scan', {folder: folder}).then(function(d) {
        var info = document.getElementById('pf-folder-info');
        if (d.ok) {
            info.innerHTML = '<span style="color:var(--success);">✓ Найдено файлов: ' + d.count + '</span> (примеры: ' + (d.samples || []).join(', ') + ')';
        } else {
            info.innerHTML = '<span style="color:var(--danger);">✗ ' + (d.error || 'Ошибка') + '</span>';
        }
    }).catch(function(e) {
        document.getElementById('pf-folder-info').innerHTML = '<span style="color:var(--danger);">✗ ' + (e.error || e) + '</span>';
    });
}

function openAvatarFolder() {
    var folder = (document.getElementById('pf-avatar-folder') || {}).value || '';
    if (!folder.trim()) {
        showToast('Введи путь к папке', 'warning');
        return;
    }
    API.post('/api/profile/avatar_folder_open', {folder: folder}).then(function(d) {
        if (!d.ok) showToast(d.error || 'Не удалось открыть', 'error');
    });
}

function previewAvatar(input) {
    var file = input.files[0];
    if (!file) return;
    document.getElementById('pf-avatar-name').textContent = file.name;
    var reader = new FileReader();
    reader.onload = function(e) {
        var img = document.getElementById('pf-avatar-img');
        img.src = e.target.result;
        document.getElementById('pf-avatar-preview').style.display = 'block';
    };
    reader.readAsDataURL(file);
}

function applyProfile() {
    var genName = document.getElementById('pf-gen-name').checked;
    var genSurname = document.getElementById('pf-gen-surname').checked;
    var genUsername = (document.getElementById('pf-gen-username') || {}).checked;
    var fn = genName ? '' : document.getElementById('pf-firstname').value.trim();
    var ln = genSurname ? '' : document.getElementById('pf-lastname').value.trim();
    var un = genUsername ? '' : ((document.getElementById('pf-username') || {}).value || '').trim().replace(/^@/, '');
    var about = document.getElementById('pf-about').value.trim();
    var emojiAvatar = (document.getElementById('pf-avatar-emoji') || {}).checked;

    if (!fn && !ln && !un && !about && !genName && !genSurname && !genUsername && !emojiAvatar && !document.getElementById('pf-avatar-input').files.length) {
        showToast('Заполните хотя бы одно поле или включите генерацию', 'warning');
        return;
    }

    var data = {};
    if (fn) data.first_name = fn;
    if (ln) data.last_name = ln;
    if (un) data.username = un;
    if (about) data.about = about;
    if (genName) data.gen_name = true;
    if (genSurname) data.gen_surname = true;
    if (genUsername) data.gen_username = true;

    var resultEl = document.getElementById('pf-result');
    resultEl.style.display = 'block';
    resultEl.innerHTML = '<span class="text-muted">Обновляю профиль...</span>';

    var chain = Promise.resolve();

    if (fn || ln || un || about || genName || genSurname || genUsername) {
        chain = chain.then(function() {
            return API.post('/api/profile/update', data);
        }).then(function(d) {
            if (d.ok) {
                resultEl.innerHTML = '<span class="text-success">✓ ' + (d.changes || []).join(', ') + '</span>';
            } else {
                resultEl.innerHTML = '<span class="text-danger">✗ ' + (d.error || 'Ошибка') + '</span>';
            }
        });
    }

    // Then avatar
    var avatarFile = document.getElementById('pf-avatar-input').files[0];
    var folderEnabled = (document.getElementById('pf-avatar-folder-enabled') || {}).checked;
    var folderPath = ((document.getElementById('pf-avatar-folder') || {}).value || '').trim();

    if (emojiAvatar) {
        // Серверная генерация png из эмодзи + цвета.
        var emo = ((document.getElementById('pf-avatar-emoji-char') || {}).value || '').trim();
        var bg = ((document.getElementById('pf-avatar-emoji-bg') || {}).value || '#3b82f6');
        chain = chain.then(function() {
            return API.post('/api/profile/avatar_emoji', {emoji: emo, bg: bg});
        }).then(function(d) {
            if (d.ok) {
                resultEl.innerHTML += '<br><span class="text-success">✓ Эмодзи-аватарка установлена (' + (d.used_emoji || '?') + ')</span>';
            } else {
                resultEl.innerHTML += '<br><span class="text-danger">✗ Эмодзи-аватарка: ' + (d.error || 'Ошибка') + '</span>';
            }
        });
    } else if (folderEnabled && folderPath) {
        chain = chain.then(function() {
            return API.post('/api/profile/avatar_from_folder', {folder: folderPath});
        }).then(function(d) {
            if (d.ok) {
                resultEl.innerHTML += '<br><span class="text-success">✓ Аватарка из папки: ' + (d.file || '?') + '</span>';
            } else {
                resultEl.innerHTML += '<br><span class="text-danger">✗ Из папки: ' + (d.error || 'Ошибка') + '</span>';
            }
        });
    } else if (avatarFile) {
        chain = chain.then(function() {
            var fd = new FormData();
            fd.append('file', avatarFile);
            return fetch('/api/profile/avatar', {method: 'POST', body: fd}).then(function(r) { return r.json(); });
        }).then(function(d) {
            if (d.ok) {
                resultEl.innerHTML += '<br><span class="text-success">✓ Аватарка обновлена</span>';
            } else {
                resultEl.innerHTML += '<br><span class="text-danger">✗ Аватарка: ' + (d.error || 'Ошибка') + '</span>';
            }
        });
    }

    chain.then(function() {
        _avatarCacheBust = Date.now();
        loadAccounts();
        playSound('success');
    }).catch(function(e) {
        resultEl.innerHTML = '<span class="text-danger">✗ ' + e + '</span>';
    });
}

function applyProfileAll() {
    var genName = document.getElementById('pf-gen-name').checked;
    var genSurname = document.getElementById('pf-gen-surname').checked;
    var fn = genName ? '' : document.getElementById('pf-firstname').value.trim();
    var ln = genSurname ? '' : document.getElementById('pf-lastname').value.trim();
    var about = document.getElementById('pf-about').value.trim();

    if (!fn && !ln && !about && !genName && !genSurname) {
        showToast('Заполните хотя бы одно поле или включите генерацию', 'warning');
        return;
    }

    var data = {};
    if (fn) data.first_name = fn;
    if (ln) data.last_name = ln;
    if (about) data.about = about;
    if (genName) data.gen_name = true;
    if (genSurname) data.gen_surname = true;

    var resultEl = document.getElementById('pf-result');
    resultEl.style.display = 'block';
    resultEl.innerHTML = '<span class="text-muted">Заполнение запущено...</span>';

    // Берём акки из селектора: «Один» — активный, «Выбрать» — отмеченные
    // чекбоксами, «Все» — все доступные.
    var phones = getSelectorPhones('acc-selector-profiles');
    if (!phones.length) {
        showToast('Не выбраны аккаунты. Включите режим «Все» или «Выбрать».', 'warning');
        return;
    }
    data.phones = phones;
    API.post('/api/profile/fill_bulk', data).then(function(d) {
        if (d.ok) {
            resultEl.innerHTML = '<span class="text-success">✓ Запущено для ' + d.total + ' акков (каждому — разные данные)</span>';
            showToast('Заполнение запущено для ' + d.total + ' акков', 'success');
        }
    }).catch(function(e) { showToast('Ошибка: ' + e, 'error'); });
}

// ══════════════════════════════════════════════════════════════════
//  AUTO-RESPONDER
// ══════════════════════════════════════════════════════════════════

var _arPollTimer = null;

function startAutoReply() {
    var text = document.getElementById('ar-text').value.trim();
    if (!text) { showToast('Введите текст ответа', 'warning'); return; }
    var delayMin = parseFloat(document.getElementById('ar-delay-min').value) || 3;
    var delayMax = parseFloat(document.getElementById('ar-delay-max').value) || 8;

    var phones = getSelectorPhones('acc-selector-autoreply');
    if (!phones.length) {
        showToast('Не выбраны аккаунты. Переключи режим на «Выбрать» и отметь нужные.', 'warning');
        return;
    }
    var backfillEl = document.getElementById('ar-backfill-hours');
    var backfillHours = backfillEl ? parseFloat(backfillEl.value) || 0 : 0;
    var swEl = document.getElementById('ar-stop-words');
    var stopWords = swEl ? swEl.value.trim() : '';
    var typingEl = document.getElementById('ar-simulate-typing');
    var readEl = document.getElementById('ar-mark-as-read');
    API.post('/api/autoresponder/start', {
        phones: phones, text: text, delay_min: delayMin, delay_max: delayMax,
        once_per_user: true, backfill_hours: backfillHours,
        stop_words: stopWords,
        simulate_typing: typingEl && typingEl.checked,
        mark_as_read: readEl && readEl.checked
    }).then(function(d) {
        if (d && d.ok) {
            var msg = 'Автоответчик запущен на ' + d.started + ' акков';
            if (d.backfill_replies) msg += ' (подхвачено ' + d.backfill_replies + ' ответов из истории)';
            showToast(msg, 'success');
            document.getElementById('btn-ar-start').style.display = 'none';
            document.getElementById('btn-ar-stop').style.display = '';
            _arPollTimer = setInterval(pollAutoReply, 3000);
        }
    }).catch(function(e) { showToast('Ошибка: ' + e.message, 'error'); });
}

function stopAutoReply() {
    API.get('/api/accounts').then(function(d) {
        var phones = (d.accounts || []).map(function(a) { return a.phone; });
        return API.post('/api/autoresponder/stop', {phones: phones});
    }).then(function(d) {
        if (d && d.ok) {
            showToast('Остановлен. Ответил ' + d.replied + ' юзерам', 'success');
            document.getElementById('btn-ar-start').style.display = '';
            document.getElementById('btn-ar-stop').style.display = 'none';
            clearInterval(_arPollTimer);
            document.getElementById('ar-status').innerHTML = '';
        }
    });
}

function pollAutoReply() {
    API.get('/api/autoresponder/status').then(function(d) {
        var el = document.getElementById('ar-status');
        var resp = d.responders || {};
        var phones = Object.keys(resp);
        if (!phones.length) {
            el.innerHTML = '<span class="text-muted text-sm">Нет активных автоответчиков</span>';
            return;
        }
        var html = '<div class="text-sm" style="border:1px solid var(--border);border-radius:8px;padding:10px;">';
        html += '<div class="fw-bold mb-2" style="color:var(--success);"><i class="bi bi-reply"></i> Активно: ' + phones.length + ' акков</div>';
        phones.forEach(function(phone) {
            var r = resp[phone];
            var uptime = r.uptime > 3600 ? Math.floor(r.uptime/3600) + 'ч' : Math.floor(r.uptime/60) + 'мин';
            html += '<div style="padding:3px 0;font-size:0.7rem;color:var(--text-muted);">+' + phone + ' — ответил ' + r.replied + ' юзерам, ' + uptime + '</div>';
        });
        html += '</div>';
        el.innerHTML = html;
    });
}


// ═══ Warmup (Прогрев) ═══════════════════════════════════════════════════════

var _warmupLoaded = false;

function loadWarmup() {
    API.get('/api/warmup/status').then(function(d) {
        _warmupLoaded = true;
        _renderWarmupAccounts(d.accounts || []);
        _renderWarmupStatus(d);
        _renderWarmupPairs(d.pairs || []);
        _renderWarmupLog(d.log || []);
        // Подсказка под селектом «Генерация текстов»: видно ли настроен AI.
        try { onWarmupGenModeChange(); } catch (e) {}
    }).catch(function() {});

    // AI-пул статус для warmup-ai-pool-hint
    try { loadAiPoolStatus(); } catch (e) {}
    // GitHub URL/pool статус
    try { loadGithubStatus(); } catch (e) {}

    loadOwnEntities();

    // Load config
    API.get('/api/warmup/config').then(function(d) {
        var el;
        el = document.getElementById('warmup-gemini-key');
        if (el && d.gemini_api_key) el.value = d.gemini_api_key;
        el = document.getElementById('warmup-gen-mode');
        if (el && d.warmup_gen_mode) el.value = d.warmup_gen_mode;
        el = document.getElementById('warmup-hours-start');
        if (el) el.value = d.warmup_active_start || 9;
        el = document.getElementById('warmup-hours-end');
        if (el) el.value = d.warmup_active_end || 22;
        el = document.getElementById('warmup-delay-min');
        if (el) el.value = d.warmup_min_delay || 60;
        el = document.getElementById('warmup-delay-max');
        if (el) el.value = d.warmup_max_delay || 300;
        el = document.getElementById('warmup-auto-move');
        if (el) el.checked = d.warmup_auto_move !== false;
        // Quiet hours (мёртвые часы по локальному времени аккаунта)
        el = document.getElementById('warmup-quiet-enabled');
        if (el) el.checked = d.warmup_quiet_hours_enabled !== false;
        el = document.getElementById('warmup-quiet-start');
        if (el) el.value = (typeof d.warmup_quiet_hours_start === 'number') ? d.warmup_quiet_hours_start : 23;
        el = document.getElementById('warmup-quiet-end');
        if (el) el.value = (typeof d.warmup_quiet_hours_end === 'number') ? d.warmup_quiet_hours_end : 7;
    }).catch(function() {});

    // Load blacklist
    API.get('/api/warmup/blacklist').then(function(d) {
        var el = document.getElementById('warmup-blacklist');
        if (el && d.text) el.value = d.text;
    }).catch(function() {});
}

function _renderWarmupAccounts(accounts) {
    var el = document.getElementById('warmup-accounts-table');
    if (!el) return;
    if (!accounts.length) {
        el.innerHTML = '<tr><td colspan="7" style="text-align:center;color:var(--text-muted);padding:16px;">' +
            '<i class="bi bi-inbox"></i> Переместите аккаунты в папку "Прогрев"</td></tr>';
        return;
    }
    var html = '';
    accounts.forEach(function(a) {
        html += '<tr>' +
            '<td style="font-size:0.75rem;white-space:nowrap;">' + (a.flag ? flagImg(a.flag) + ' ' : '') + '+' + escHtml(a.phone) + '</td>' +
            '<td style="font-size:0.75rem;">' + escHtml(a.first_name || a.username || '') + '</td>' +
            '<td style="font-size:0.75rem;" title="Источник: ' + escHtml(a.age_source || 'unknown') + '">' + escHtml(a.age_text) + '<i class="bi bi-info-circle" style="color:var(--text-muted);font-size:0.6rem;margin-left:4px;opacity:0.6;"></i></td>' +
            '<td style="font-size:0.75rem;">' + (a.is_premium ? '<span style="color:#f97316;"><i class="bi bi-star-fill"></i></span>' : '—') + '</td>' +
            '<td style="font-size:0.75rem;">' + (a.has_proxy ? '<span style="color:#34d399;">✓</span>' : '<span style="color:var(--danger);">✗</span>') + '</td>' +
            '<td style="font-size:0.75rem;">' + escHtml(a.folder) + '</td>' +
            '<td style="font-size:0.7rem;text-align:right;">' +
                '<button class="btn btn-outline btn-sm" onclick="resetAccDay(\'' + a.phone + '\')" style="padding:2px 6px;font-size:0.65rem;" title="Сбросить на день 1">♻ День 1</button>' +
            '</td>' +
        '</tr>';
    });
    el.innerHTML = html;

    // Обновить dropdown для hub-акка
    var hubSel = document.getElementById('warmup-hub-phone');
    if (hubSel) {
        var currentVal = hubSel.value;
        var opts = '<option value="">Первый в списке</option>';
        accounts.forEach(function(a) {
            opts += '<option value="' + a.phone + '">+' + a.phone + ' ' + escHtml(a.first_name || a.username || '') + '</option>';
        });
        hubSel.innerHTML = opts;
        if (currentVal) hubSel.value = currentVal;
    }
}

function resetAccDay(phone) {
    if (!confirm('Сбросить аккаунт +' + phone + ' на день 1?')) return;
    API.post('/api/warmup/reset_acc_day', { phone: phone }).then(function(d) {
        if (d.ok) showToast('Акк +' + phone + ' сброшен на день 1', 'success');
        else showToast(d.error || 'Ошибка', 'error');
    });
}

function onWarmupModeChange() {
    var mode = (document.getElementById('warmup-mode') || {}).value || 'classic';
    var hubWrap = document.getElementById('warmup-hub-wrap');
    var chatWrap = document.getElementById('warmup-chat-opts-wrap');
    if (hubWrap) hubWrap.style.display = mode === 'hub' ? '' : 'none';
    if (chatWrap) chatWrap.style.display = mode === 'chat_flow' ? '' : 'none';
    var hint = document.getElementById('warmup-mode-hint');
    if (hint) {
        if (mode === 'classic') hint.title = 'Classic: акки разбиваются на пары и общаются в ЛС друг с другом. Самый естественный вариант.';
        else if (mode === 'hub') hint.title = 'Hub: выбранный акк добавляет всех в контакты и пишет им первым. Подходит когда нужен 1 "главный" акк.';
        else if (mode === 'chat_flow') hint.title = 'Chat-flow: все акки общаются в одном общем чате прогрева. Сначала создайте чат в сценарии "Свой чат" ниже.';
    }
}

function _renderWarmupStatus(d) {
    var banner = document.getElementById('warmup-status-banner');
    var schedule = document.getElementById('warmup-schedule');
    var startBtn = document.getElementById('warmup-start-btn');
    var pauseBtn = document.getElementById('warmup-pause-btn');
    var stopBtn = document.getElementById('warmup-stop-btn');
    var daysSelect = document.getElementById('warmup-days');

    if (!d.running) {
        if (banner) banner.style.display = 'none';
        if (schedule) schedule.style.display = 'none';
        if (startBtn) startBtn.style.display = '';
        if (pauseBtn) pauseBtn.style.display = 'none';
        if (stopBtn) stopBtn.style.display = 'none';
        if (daysSelect) daysSelect.disabled = false;
        return;
    }

    if (startBtn) startBtn.style.display = 'none';
    if (pauseBtn) { pauseBtn.style.display = ''; pauseBtn.innerHTML = d.status === 'paused' ? '<i class="bi bi-play-fill"></i> Продолжить' : '<i class="bi bi-pause-fill"></i> Пауза'; }
    if (stopBtn) stopBtn.style.display = '';
    if (daysSelect) daysSelect.disabled = true;

    var s = d.schedule || {};
    if (schedule) {
        schedule.style.display = '';
        var stageText = document.getElementById('warmup-stage-text');
        if (stageText) stageText.textContent = (s.stage_icon || '') + ' ' + (s.stage_label || '');
        var dayText = document.getElementById('warmup-day-text');
        if (dayText) dayText.textContent = 'День ' + s.current_day + '/' + s.total_days;
        var bar = document.getElementById('warmup-progress-bar');
        if (bar) {
            bar.style.width = (s.pct || 0) + '%';
            if (d.status === 'running') bar.classList.add('is-running');
            else bar.classList.remove('is-running');
        }
        var mt = document.getElementById('warmup-msgs-today');
        if (mt) mt.textContent = d.messages_today || 0;
        var ml = document.getElementById('warmup-msgs-limit');
        if (ml) ml.textContent = s.msgs_per_day || 0;
        var total = document.getElementById('warmup-msgs-total');
        if (total) total.textContent = d.total_messages || 0;
    }

    if (banner) {
        var color = d.status === 'paused' ? '#f59e0b' : '#34d399';
        var label = d.status === 'paused' ? 'Пауза' : 'Работает';
        banner.style.display = '';
        banner.innerHTML = '<div style="background:' + color + '20;border:1px solid ' + color + '40;border-radius:8px;padding:8px 12px;font-size:0.8rem;color:' + color + ';">' +
            '<b>' + label + '</b> — ' + (s.stage_icon || '') + ' ' + (s.stage_label || '') +
            ' | День ' + (s.current_day || 1) + '/' + (s.total_days || 21) +
            ' | ' + (s.max_peers || 5) + ' юзов | ' + (d.total_messages || 0) + ' сообщений</div>';
    }
}

function _renderWarmupPairs(pairs) {
    var section = document.getElementById('warmup-pairs-section');
    var el = document.getElementById('warmup-pairs-list');
    if (!el || !section) return;
    if (!pairs.length) { section.style.display = 'none'; return; }
    section.style.display = '';
    var html = '';
    pairs.forEach(function(p) {
        var stateColor = p.state === 'chatting' ? '#34d399' : p.state === 'done' ? 'var(--text-muted)' : '#f59e0b';
        html += '<div style="display:flex;align-items:center;gap:8px;padding:4px 0;font-size:0.72rem;">' +
            '<span>+' + p.phone_a + '</span>' +
            '<span style="color:' + stateColor + ';">↔</span>' +
            '<span>+' + p.phone_b + '</span>' +
            '<span style="color:var(--text-muted);">' + (p.topic || '') + '</span>' +
            '<span style="color:var(--text-muted);">' + p.exchanged + ' обменов</span>' +
            '<span style="color:' + stateColor + ';font-size:0.65rem;">' + p.state + '</span>' +
        '</div>';
    });
    el.innerHTML = html;
}

function _renderWarmupLog(log) {
    var section = document.getElementById('warmup-log-section');
    var el = document.getElementById('warmup-log');
    if (!el) return;
    if (!log || !log.length) {
        if (section) section.style.display = 'none';
        return;
    }
    if (section) section.style.display = '';
    el.value = log.join('\n');
    el.scrollTop = el.scrollHeight;
}

function _getScenariosPayload() {
    var chansText = (document.getElementById('scn-channels-list') || {}).value || '';
    return {
        scenarios: {
            dm: (document.getElementById('scn-dm') || {}).checked !== false,
            channels: !!(document.getElementById('scn-channels') || {}).checked,
            reactions: !!(document.getElementById('scn-reactions') || {}).checked,
            own_chat: !!(document.getElementById('scn-own-chat') || {}).checked,
            diaries: !!(document.getElementById('scn-diaries') || {}).checked,
        },
        reply_to_incoming: (document.getElementById('scn-reply') || {}).checked !== false,
        channels_list: chansText.split('\n').map(function(s){return s.trim()}).filter(Boolean),
        channels_per_day: parseInt((document.getElementById('scn-channels-per-day') || {}).value) || 3,
        reactions_per_day: parseInt((document.getElementById('scn-reactions-per-day') || {}).value) || 5,
        // Сообщений ЛС/день на акк. 0 = авто (по дню/неделе прогрева).
        // Если задан — будет применяться весь цикл независимо от дня.
        dm_msgs_per_day_per_acc: Math.max(0, Math.min(200, parseInt((document.getElementById('scn-dm-msgs-per-day') || {}).value) || 0)),
    };
}

function startWarmup() {
    var days = parseInt(document.getElementById('warmup-days').value) || 21;
    var startDay = parseInt((document.getElementById('warmup-start-day') || {}).value) || 1;
    if (startDay < 1) startDay = 1;
    if (startDay > days) {
        showToast('Стартовый день не может быть больше всего дней (' + days + ')', 'warning');
        return;
    }
    var genMode = (document.getElementById('warmup-gen-mode') || {}).value || 'templates';
    var startH = parseInt((document.getElementById('warmup-hours-start') || {}).value) || 9;
    var endH = parseInt((document.getElementById('warmup-hours-end') || {}).value) || 22;
    var minDelay = parseInt((document.getElementById('warmup-delay-min') || {}).value) || 60;
    var maxDelay = parseInt((document.getElementById('warmup-delay-max') || {}).value) || 300;
    var autoMove = (document.getElementById('warmup-auto-move') || {}).checked !== false;

    var msgsOverride = parseInt((document.getElementById('warmup-msgs-override') || {}).value) || 0;
    if (msgsOverride < 0) msgsOverride = 0;
    if (msgsOverride > 500) msgsOverride = 500;

    var qhStartW = parseInt((document.getElementById('warmup-quiet-start') || {}).value);
    var qhEndW = parseInt((document.getElementById('warmup-quiet-end') || {}).value);
    var payload = {
        total_days: days,
        start_day: startDay,
        msgs_per_day_override: msgsOverride,
        auto_move: autoMove,
        gen_mode: genMode,
        active_hours_start: startH,
        active_hours_end: endH,
        min_delay: minDelay,
        max_delay: maxDelay,
        threads: parseInt((document.getElementById('warmup-threads') || {}).value) || 1,
        work_until_midnight: !!(document.getElementById('warmup-until-midnight') || {}).checked,
        manual_next_day: !!(document.getElementById('warmup-manual-next-day') || {}).checked,
        warmup_mode: (document.getElementById('warmup-mode') || {}).value || 'classic',
        hub_phone: (document.getElementById('warmup-hub-phone') || {}).value || '',
        id_format: !!(document.getElementById('warmup-id-format') || {}).checked,
        duplicate_dm_after_reply: (document.getElementById('warmup-duplicate-dm') || {}).checked !== false,
        quiet_hours_enabled: (document.getElementById('warmup-quiet-enabled') || {}).checked !== false,
        quiet_hours_start: isNaN(qhStartW) ? 23 : qhStartW,
        quiet_hours_end: isNaN(qhEndW) ? 7 : qhEndW,
    };
    var sc = _getScenariosPayload();
    for (var k in sc) payload[k] = sc[k];

    payload.ad_config = readAdConfig('warmup');
    API.post('/api/warmup/start', payload).then(function(d) {
        if (d.ok) {
            showToast('Прогрев запущен: ' + d.pairs + ' пар, ' + d.days + ' дней', 'success');
            loadWarmup();
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    }).catch(function() { showToast('Ошибка запуска', 'error'); });
}

function stopWarmup() {
    API.post('/api/warmup/stop').then(function(d) {
        if (d.ok) { showToast('Прогрев остановлен', 'info'); loadWarmup(); }
    });
}

function pauseWarmup() {
    API.post('/api/warmup/pause').then(function(d) {
        if (d.ok) {
            showToast(d.status === 'paused' ? 'Прогрев на паузе' : 'Прогрев возобновлён', 'info');
            loadWarmup();
        }
    });
}

function moveSelectedToFolder() {
    var phones = [];
    document.querySelectorAll('.acc-check:checked').forEach(function(cb) {
        phones.push(cb.value);
    });
    if (!phones.length) { showToast('Отметьте аккаунты', 'warning'); return; }

    API.get('/api/accounts/folders').then(function(d) {
        var folders = d.folders || [];
        if (!folders.length) { showToast('Нет доступных папок', 'error'); return; }
        var modal = document.createElement('div');
        modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:500;display:flex;align-items:center;justify-content:center;';
        modal.onclick = function(e) { if (e.target === modal) modal.remove(); };
        modal.innerHTML = '<div style="background:var(--bg-card-hover);border:1px solid var(--border);border-radius:14px;padding:20px;max-width:380px;width:90%;">' +
            '<h3 style="color:#fff;font-size:1.05rem;margin-bottom:12px;">Перенести ' + phones.length + ' аккаунтов в папку</h3>' +
            '<select id="_move-folder-sel" class="form-control mb-3" style="font-size:0.85rem;">' +
            folders.map(function(f) { return '<option value="' + escHtml(f) + '">' + escHtml(f) + '</option>'; }).join('') +
            '</select>' +
            '<div class="d-flex gap-2"><button class="btn btn-accent btn-sm" onclick="_moveDoIt()">Перенести</button>' +
            '<button class="btn btn-outline btn-sm" onclick="this.closest(\'div[style*=fixed]\').remove();">Отмена</button></div>' +
        '</div>';
        modal.id = '_move-modal';
        window._movePhones = phones;
        document.body.appendChild(modal);
    });
}

function _moveDoIt() {
    var sel = document.getElementById('_move-folder-sel');
    var folder = sel ? sel.value : '';
    if (!folder) return;
    API.post('/api/accounts/move_to_folder', { phones: window._movePhones || [], folder: folder }).then(function(d) {
        if (d.ok) {
            showToast('Перенесено: ' + d.moved, 'success');
            loadAccounts();
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
        var m = document.getElementById('_move-modal');
        if (m) m.remove();
    });
}

function warmupNextDay() {
    if (!confirm('Запустить следующий день прогрева?')) return;
    API.post('/api/warmup/next_day').then(function(d) {
        if (d.ok) {
            showToast(d.msg || 'Следующий день запущен', 'success');
            loadWarmup();
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    });
}

function saveWarmupConfig() {
    var qhStart = parseInt((document.getElementById('warmup-quiet-start') || {}).value);
    var qhEnd = parseInt((document.getElementById('warmup-quiet-end') || {}).value);
    var data = {
        gemini_api_key: (document.getElementById('warmup-gemini-key') || {}).value || '',
        warmup_gen_mode: (document.getElementById('warmup-gen-mode') || {}).value || 'templates',
        warmup_active_start: parseInt((document.getElementById('warmup-hours-start') || {}).value) || 9,
        warmup_active_end: parseInt((document.getElementById('warmup-hours-end') || {}).value) || 22,
        warmup_min_delay: parseInt((document.getElementById('warmup-delay-min') || {}).value) || 60,
        warmup_max_delay: parseInt((document.getElementById('warmup-delay-max') || {}).value) || 300,
        warmup_auto_move: (document.getElementById('warmup-auto-move') || {}).checked !== false,
        warmup_quiet_hours_enabled: (document.getElementById('warmup-quiet-enabled') || {}).checked !== false,
        warmup_quiet_hours_start: isNaN(qhStart) ? 23 : qhStart,
        warmup_quiet_hours_end: isNaN(qhEnd) ? 7 : qhEnd,
    };
    API.post('/api/warmup/config', data).then(function(d) {
        if (d.ok) showToast('Настройки сохранены', 'success');
    });
}

function checkGeminiKey() {
    var key = (document.getElementById('warmup-gemini-key') || {}).value || '';
    if (!key) { showToast('Введите ключ', 'warning'); return; }
    showToast('Проверяю...', 'info');
    API.post('/api/warmup/check_key', { api_key: key }).then(function(d) {
        if (d.ok) showToast('Ключ работает!', 'success');
        else showToast('Ошибка: ' + (d.error || 'неизвестно'), 'error');
    }).catch(function() { showToast('Ошибка проверки', 'error'); });
}

function saveWarmupBlacklist() {
    var text = (document.getElementById('warmup-blacklist') || {}).value || '';
    API.post('/api/warmup/blacklist', { text: text }).then(function(d) {
        if (d.ok) showToast('Чёрный список сохранён', 'success');
    });
}

function fetchGithubChannels() {
    var urlEl = document.getElementById('warmup-github-url');
    var url = (urlEl && urlEl.value || '').trim();
    if (!url) { showToast('Укажите URL списка каналов', 'warning'); return; }
    if (!/^https?:\/\//.test(url)) { showToast('URL должен начинаться с http:// или https://', 'warning'); return; }
    showToast('Скачиваю список...', 'info');
    API.post('/api/warmup/fetch_github_channels', { url: url }).then(function(d) {
        if (d.ok) {
            showToast('Загружено ' + d.fetched + ' каналов. В пуле: ' + d.total_in_pool, 'success');
            loadDefaultChannels();
            loadGithubStatus();
        } else {
            showToast(d.error || 'Не удалось скачать', 'error');
            if (d.details && console) console.warn('Fetch errors:', d.details);
        }
    }).catch(function() { showToast('Нет связи', 'error'); });
}

function clearGithubPool() {
    if (!confirm('Очистить локальный пул каналов скачанных по ссылке?')) return;
    API.post('/api/warmup/clear_github_pool', {}).then(function(d) {
        if (d.ok) {
            showToast('Пул очищен', 'success');
            loadGithubStatus();
        }
    });
}

function loadGithubStatus() {
    API.get('/api/warmup/github_status').then(function(d) {
        var urlEl = document.getElementById('warmup-github-url');
        if (urlEl && !urlEl.value && d.url) urlEl.value = d.url;
        var st = document.getElementById('warmup-github-status');
        if (st) {
            if (d.pool_size > 0) {
                st.innerHTML = '<i class="bi bi-check-circle" style="color:var(--success);"></i> В пуле ' + d.pool_size + ' каналов из вашего источника';
            } else {
                st.innerHTML = '';
            }
        }
    }).catch(function() {});
}

function loadDefaultChannels() {
    API.get('/api/warmup/defaults').then(function(d) {
        var el = document.getElementById('scn-channels-list');
        if (el && d.channels) {
            var existing = (el.value || '').split('\n').map(function(s){return s.trim()}).filter(Boolean);
            var merged = existing.concat(d.channels).filter(function(v, i, a){ return a.indexOf(v) === i; });
            el.value = merged.join('\n');
            showToast('Загружено ' + d.channels.length + ' каналов', 'success');
        }
    });
}

function createWarmupChat() {
    var subgroupSize = parseInt((document.getElementById('own-chat-subgroup') || {}).value);
    if (isNaN(subgroupSize) || subgroupSize < 0) subgroupSize = 0;
    var title = '';
    if (subgroupSize === 0) {
        // Один общий чат — спросим имя
        var t = prompt('Название приватного чата (оставьте пустым — выберем нейтральное автоматически):', '');
        if (t === null) return;
        title = t;
    } else {
        if (!confirm('Создать чаты подгрупп по ' + subgroupSize + ' акков? Имена будут подобраны автоматически из нейтрального пула (по одному на чат).')) return;
    }
    showToast(subgroupSize > 0 ? 'Создаю чаты подгрупп...' : 'Создаю чат...', 'info');
    API.post('/api/warmup/create_own_chat', { title: title, subgroup_size: subgroupSize }).then(function(d) {
        if (d.ok) {
            var st = document.getElementById('own-chat-status');
            if (d.groups_created > 1) {
                showToast('Создано ' + d.groups_created + ' чатов подгрупп', 'success');
                if (st) {
                    var names = d.groups.map(function(g){return '«' + escHtml(g.title) + '» (' + g.members_count + ' акк.)';}).join(', ');
                    st.innerHTML = '<i class="bi bi-check-circle" style="color:var(--success);"></i> ' + d.groups_created + ' чатов: ' + names;
                }
            } else {
                showToast('Чат создан', 'success');
                if (st && d.groups && d.groups[0]) {
                    st.innerHTML = '<i class="bi bi-check-circle" style="color:var(--success);"></i> ' + escHtml(d.groups[0].title) + ' (ID: ' + d.groups[0].chat_id + ')';
                }
            }
            var cb = document.getElementById('scn-own-chat');
            if (cb) cb.checked = true;
        } else {
            showToast(d.error || 'Не удалось создать', 'error');
        }
    }).catch(function() { showToast('Ошибка создания чата', 'error'); });
}

function refreshWarmupChat() {
    showToast('Догоняю участников (до 2 минут)...', 'info');
    API.post('/api/warmup/refresh_own_chat', {}).then(function(d) {
        if (d.ok) {
            var msg = d.joined > 0
                ? 'Подключено в чат: ' + d.joined + ' акк.'
                : 'Все warmup-акки уже в чате';
            showToast(msg, 'success');
            var st = document.getElementById('own-chat-status');
            if (st && d.info) st.innerHTML = '<i class="bi bi-info-circle" style="color:var(--info);"></i> ' + escHtml(d.info);
        } else {
            showToast(d.error || 'Не удалось догнать', 'error');
        }
    }).catch(function() { showToast('Ошибка догона участников', 'error'); });
}

function createWarmupDiaries() {
    // Собираем выбранные темы
    var themes = [];
    document.querySelectorAll('.diary-theme:checked').forEach(function(el) {
        themes.push(el.value);
    });
    if (themes.length === 0) {
        showToast('Выбери хотя бы одну тему дневников', 'warning');
        return;
    }
    if (!confirm('Создать личные каналы-дневники для всех прогреваемых аккаунтов? Это займёт несколько минут (~5-10 сек на акк).')) return;
    showToast('Создаю дневники... (несколько минут)', 'info');
    var st = document.getElementById('diaries-status');
    if (st) st.innerHTML = '<i class="bi bi-arrow-clockwise" style="animation:spin 1s linear infinite;"></i> Создание...';
    API.post('/api/warmup/create_diaries', { themes: themes }).then(function(d) {
        if (d.ok) {
            showToast('Создано дневников: ' + (d.created || 0) + ' / пропущено: ' + (d.skipped || 0) + ' / ошибок: ' + (d.failed || 0), 'success');
            var cb = document.getElementById('scn-diaries');
            if (cb) cb.checked = true;
            loadDiariesStatus();
        } else {
            showToast(d.error || 'Не удалось создать', 'error');
            if (st) st.innerHTML = '<i class="bi bi-exclamation-triangle" style="color:var(--danger);"></i> ' + escHtml(d.error || 'Ошибка');
        }
    }).catch(function() { showToast('Ошибка создания дневников', 'error'); });
}

function loadDiariesStatus() {
    var st = document.getElementById('diaries-status');
    if (!st) return;
    API.get('/api/warmup/diaries_status').then(function(d) {
        var total = d.total || 0;
        if (total === 0) {
            st.innerHTML = '<span style="color:var(--text-muted);">Дневники ещё не созданы</span>';
            return;
        }
        var byTheme = {};
        (d.diaries || []).forEach(function(it) {
            byTheme[it.theme] = (byTheme[it.theme] || 0) + 1;
        });
        var labels = { work: 'Работа', learning: 'Учёба', travel: 'Путешествия', sport: 'Спорт', freelance: 'Фриланс' };
        var parts = Object.keys(byTheme).map(function(k){ return (labels[k] || k) + ': ' + byTheme[k]; });
        st.innerHTML = '<i class="bi bi-check-circle" style="color:var(--success);"></i> Создано <b>' + total + '</b> дневников. ' + parts.join(' · ');
    }).catch(function(){
        st.innerHTML = '<span style="color:var(--danger);">Не удалось загрузить статус</span>';
    });
}

function clearDiariesRecord() {
    if (!confirm('Сбросить запись о дневниках в настройках? Сами каналы в Телеграме не удалятся — их можно будет вручную удалить через Telegram. После сброса можно создать дневники заново.')) return;
    API.post('/api/warmup/diaries_clear', {}).then(function(d) {
        if (d.ok) {
            showToast('Запись о дневниках сброшена', 'success');
            loadDiariesStatus();
        }
    }).catch(function(){ showToast('Ошибка', 'error'); });
}

function loadOwnEntities() {
    API.get('/api/warmup/own_entities').then(function(d) {
        if (d.own_chat_id) {
            var st = document.getElementById('own-chat-status');
            if (st) st.innerHTML = '<i class="bi bi-check-circle" style="color:var(--success);"></i> ' + escHtml(d.own_chat_title || 'Чат') + ' создан (ID: ' + d.own_chat_id + ')';
        }
    }).catch(function(){});
    loadDiariesStatus();
}

function saveScenarios() {
    // Сохраняем в user_settings как JSON
    var payload = _getScenariosPayload();
    API.post('/api/warmup/config', {
        warmup_scenarios_json: JSON.stringify(payload),
    }).then(function(d) {
        if (d.ok) showToast('Сценарии сохранены. Они применятся при следующем запуске прогрева.', 'success');
    });
}

// ══════════════════════════════════════════════════════════════════════
// REAUTH (Переавторизация)
// ══════════════════════════════════════════════════════════════════════

var _reauthSelected = [];
var _s2tSelected = [];
var _reauthStatusTimer = null;

function loadReauth() {
    // Загружаем custom preset
    API.get('/api/reauth/custom_preset').then(function(d) {
        var c = d.custom || {};
        if (c.api_id) document.getElementById('cp-api-id').value = c.api_id;
        if (c.api_hash) document.getElementById('cp-api-hash').value = c.api_hash;
        if (c.device_model) document.getElementById('cp-device').value = c.device_model;
        if (c.system_version) document.getElementById('cp-sysver').value = c.system_version;
        if (c.app_version) document.getElementById('cp-appver').value = c.app_version;
        if (c.lang_code) document.getElementById('cp-lang').value = c.lang_code;
        if (c.system_lang_code) document.getElementById('cp-syslang').value = c.system_lang_code;
    });
    _startReauthPolling();
}

function _startReauthPolling() {
    if (_reauthStatusTimer) clearInterval(_reauthStatusTimer);
    _reauthStatusTimer = setInterval(_pollReauthStatus, 2000);
    _pollReauthStatus();
}

function _pollReauthStatus() {
    API.get('/api/reauth/status').then(function(d) {
        if (!d || d.error) return;
        var running = d.status === 'running';
        var kind = d.kind || 'reauth';
        var pct = d.total > 0 ? (d.done / d.total) * 100 : 0;
        var statsText = 'OK=' + d.ok + ' FAIL=' + d.fail;
        var progressText = d.done + '/' + d.total;

        // Скрываем все прогресс-блоки
        ['reauth-progress', 't2s-progress', 's2t-progress'].forEach(function(id) {
            var el = document.getElementById(id);
            if (el && kind !== (id === 'reauth-progress' ? 'reauth' : id === 't2s-progress' ? 't2s' : 's2t')) {
                // скрываем если не наш kind И не running
                if (!running && d.done === 0) el.style.display = 'none';
            }
        });

        // Показываем нужный
        var progressId = kind === 't2s' ? 't2s-progress' : kind === 's2t' ? 's2t-progress' : 'reauth-progress';
        var barId = kind === 't2s' ? 't2s-progress-bar' : kind === 's2t' ? 's2t-progress-bar' : 'reauth-progress-bar';
        var txtId = kind === 't2s' ? 't2s-progress-text' : kind === 's2t' ? 's2t-progress-text' : 'reauth-progress-text';
        var statsId = kind === 't2s' ? 't2s-stats' : kind === 's2t' ? 's2t-stats' : 'reauth-stats';

        if (running || d.done > 0) {
            var pBlock = document.getElementById(progressId);
            if (pBlock) pBlock.style.display = '';
            var txt = document.getElementById(txtId);
            var bar = document.getElementById(barId);
            var stats = document.getElementById(statsId);
            if (txt) txt.textContent = progressText;
            if (bar) bar.style.width = pct + '%';
            if (stats) stats.textContent = statsText;
        }

        // Лог (для reauth)
        var log = document.getElementById('reauth-log');
        if (log && d.log && (kind === 'reauth' || running)) {
            log.style.display = '';
            log.value = d.log.join('\n');
        }

        // Кнопки для reauth
        var btnStart = document.getElementById('btn-reauth-start');
        var btnStop = document.getElementById('btn-reauth-stop');
        if (btnStart && btnStop && kind === 'reauth') {
            btnStart.style.display = running ? 'none' : '';
            btnStop.style.display = running ? '' : 'none';
        }
    });
}

function reauthPresetChanged() {
    // Подсказка — сейчас ничего
}

function openReauthCustomModal() {
    var m = document.getElementById('reauth-custom-modal');
    if (m) m.style.display = 'flex';
}

function saveReauthCustomPreset() {
    var data = {
        api_id: parseInt(document.getElementById('cp-api-id').value) || 0,
        api_hash: document.getElementById('cp-api-hash').value.trim(),
        device_model: document.getElementById('cp-device').value.trim(),
        system_version: document.getElementById('cp-sysver').value.trim(),
        app_version: document.getElementById('cp-appver').value.trim(),
        lang_code: document.getElementById('cp-lang').value.trim(),
        system_lang_code: document.getElementById('cp-syslang').value.trim(),
    };
    if (!data.api_id || !data.api_hash) {
        showToast('Введите api_id и api_hash', 'warning');
        return;
    }
    API.post('/api/reauth/custom_preset', data).then(function(d) {
        if (d.ok) {
            showToast('Параметры сохранены', 'success');
            document.getElementById('reauth-custom-modal').style.display = 'none';
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    });
}

function openReauthAccPicker(mode) {
    mode = mode || 'reauth';
    if (!_cachedAccounts || !_cachedAccounts.length) {
        API.get('/api/accounts').then(function(d) {
            _cachedAccounts = d.accounts || [];
            _showReauthAccPicker(mode);
        });
    } else {
        _showReauthAccPicker(mode);
    }
}

function _showReauthAccPicker(mode) {
    var selected = mode === 's2t' ? _s2tSelected : _reauthSelected;
    var listEl = document.getElementById(mode === 's2t' ? 's2t-acc-list' : 'reauth-acc-list');
    var countEl = document.getElementById(mode === 's2t' ? 's2t-selected-count' : 'reauth-selected-count');

    var modal = document.createElement('div');
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:500;display:flex;align-items:center;justify-content:center;';
    modal.onclick = function(e) { if (e.target === modal) modal.remove(); };
    var accsHtml = _cachedAccounts.map(function(a) {
        var checked = selected.indexOf(a.phone) !== -1 ? 'checked' : '';
        return '<label style="display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:6px;cursor:pointer;" onmouseover="this.style.background=\'#1a1040\';" onmouseout="this.style.background=\'none\';">' +
            '<input type="checkbox" value="' + a.phone + '" ' + checked + '>' +
            flagImg(a.flag) + ' +' + a.phone +
            (a.dead ? ' <span style="color:var(--danger);font-size:0.7rem;">мёртвый</span>' : '') +
            '</label>';
    }).join('');
    modal.innerHTML = '<div style="background:var(--bg-card-hover);border:1px solid var(--border);border-radius:14px;padding:20px;max-width:420px;width:90%;max-height:80vh;display:flex;flex-direction:column;">' +
        '<h3 style="color:#fff;font-size:1.05rem;margin-bottom:10px;">Выберите аккаунты</h3>' +
        '<div class="d-flex gap-2 mb-2"><button class="btn btn-outline btn-sm" onclick="_reauthPickerAll(true, \'' + mode + '\')">Все</button><button class="btn btn-outline btn-sm" onclick="_reauthPickerAll(false, \'' + mode + '\')">Никого</button></div>' +
        '<div id="_reauth-picker-list" style="flex:1;overflow-y:auto;margin-bottom:12px;font-size:0.82rem;">' + accsHtml + '</div>' +
        '<div class="d-flex gap-2"><button class="btn btn-accent btn-sm" onclick="_reauthPickerApply(\'' + mode + '\')">Применить</button><button class="btn btn-outline btn-sm" onclick="this.closest(\'div[style*=fixed]\').remove();">Отмена</button></div>' +
        '</div>';
    modal.id = '_reauth-picker';
    document.body.appendChild(modal);
}

function _reauthPickerAll(val, mode) {
    document.querySelectorAll('#_reauth-picker-list input[type=checkbox]').forEach(function(cb) { cb.checked = val; });
}

function _reauthPickerApply(mode) {
    var selected = [];
    document.querySelectorAll('#_reauth-picker-list input[type=checkbox]:checked').forEach(function(cb) {
        selected.push(cb.value);
    });
    if (mode === 's2t') _s2tSelected = selected; else _reauthSelected = selected;

    var listEl = document.getElementById(mode === 's2t' ? 's2t-acc-list' : 'reauth-acc-list');
    var countEl = document.getElementById(mode === 's2t' ? 's2t-selected-count' : 'reauth-selected-count');
    if (countEl) countEl.textContent = selected.length + ' выбрано';
    if (listEl) listEl.innerHTML = selected.map(function(p) {
        return '<span style="background:var(--surface-info-bg);border:1px solid var(--accent);border-radius:6px;padding:2px 8px;color:var(--accent-light);font-family:monospace;">+' + p + '</span>';
    }).join('');

    var m = document.getElementById('_reauth-picker');
    if (m) m.remove();
}

function startReauth() {
    if (!_reauthSelected.length) {
        showToast('Выберите аккаунты', 'warning');
        return;
    }
    var preset = document.getElementById('reauth-preset').value;
    var payload = {
        phones: _reauthSelected,
        preset: preset,
        new_2fa: document.getElementById('reauth-new-2fa').value,
        close_others: document.getElementById('reauth-close-others').checked,
        delete_dialog: document.getElementById('reauth-delete-dialog').checked,
        continue_on_limit: document.getElementById('reauth-continue-limit').checked,
        keep_original: document.getElementById('reauth-keep-original').checked,
    };
    API.post('/api/reauth/start', payload).then(function(d) {
        if (d.ok) {
            showToast('Запущено: ' + d.total + ' акк.', 'success');
            _pollReauthStatus();
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    });
}

function stopReauth() {
    API.post('/api/reauth/stop').then(function(d) {
        if (d.ok) showToast('Остановка...', 'info');
    });
}

function runTdataToSession() {
    var folder = document.getElementById('reauth-t2s-folder').value.trim();
    if (!folder) { showToast('Укажите путь к папке', 'warning'); return; }
    var payload = {
        folder: folder,
        keep_originals: (document.getElementById('reauth-t2s-keep') || {}).checked === true,
        password_filename: document.getElementById('reauth-t2s-pwd').value,
        depth: parseInt(document.getElementById('reauth-t2s-depth').value) || 4,
        threads: parseInt((document.getElementById('reauth-t2s-threads') || {}).value) || 5,
    };
    API.post('/api/reauth/tdata_to_session', payload).then(function(d) {
        if (d.ok) {
            showToast('Конвертация запущена', 'success');
            _pollReauthStatus();
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    });
}

function runSessionToTdata() {
    if (!_s2tSelected.length) { showToast('Выберите аккаунты', 'warning'); return; }
    var payload = {
        phones: _s2tSelected,
        pack_count: parseInt(document.getElementById('s2t-pack').value) || 1,
        offline: document.getElementById('s2t-offline').checked,
        save_2fa: document.getElementById('s2t-save-2fa').checked,
        copy_original: document.getElementById('s2t-copy-orig').checked,
        threads: parseInt((document.getElementById('s2t-threads') || {}).value) || 5,
    };
    API.post('/api/reauth/session_to_tdata', payload).then(function(d) {
        if (d.ok) {
            showToast('Конвертация запущена', 'success');
            _pollReauthStatus();
        } else {
            showToast(d.error || 'Ошибка', 'error');
        }
    });
}


// ══════════════════════════════════════════════════════════════════
//  Имитация активности — фоновое чтение / просмотр профилей
// ══════════════════════════════════════════════════════════════════

function startImitation() {
    var phones = getSelectorPhones('acc-selector-imitation');
    if (!phones.length) { showToast('Не выбраны аккаунты', 'warning'); return; }
    var payload = {
        phones: phones,
        interval_min: parseInt(document.getElementById('imit-interval-min').value || '15', 10),
        interval_max: parseInt(document.getElementById('imit-interval-max').value || '45', 10),
        actions_per_round: parseInt(document.getElementById('imit-actions-per-round').value || '3', 10),
        do_read: document.getElementById('imit-do-read').checked,
        do_view_profile: document.getElementById('imit-do-view-profile').checked,
        do_view_photo: document.getElementById('imit-do-view-photo').checked,
    };
    API.post('/api/imitation/start', payload).then(function(d) {
        if (d && d.ok) {
            showToast('Имитация запущена на ' + d.phones + ' акков', 'success');
            document.getElementById('btn-imit-start').style.display = 'none';
            document.getElementById('btn-imit-stop').style.display = '';
            loadImitationStatus();
        } else {
            showToast((d && d.error) || 'Ошибка запуска', 'error');
        }
    }).catch(function(e) { showToast('Ошибка: ' + (e && e.error || ''), 'error'); });
}

function stopImitation() {
    API.post('/api/imitation/stop', {}).then(function(d) {
        if (d && d.ok) {
            showToast('Имитация остановлена', 'success');
            document.getElementById('btn-imit-start').style.display = '';
            document.getElementById('btn-imit-stop').style.display = 'none';
            loadImitationStatus();
        }
    }).catch(function() {});
}

function loadImitationStatus() {
    API.get('/api/imitation/status').then(function(d) {
        var lbl = document.getElementById('imit-status-label');
        var counters = document.getElementById('imit-counters');
        var log = document.getElementById('imit-log');
        if (!lbl) return;
        if (d.status === 'running') {
            lbl.innerHTML = '<i class="bi bi-circle-fill" style="color:var(--success);"></i> работает (' + d.phones + ' акков)';
            document.getElementById('btn-imit-start').style.display = 'none';
            document.getElementById('btn-imit-stop').style.display = '';
        } else {
            lbl.innerHTML = '<i class="bi bi-circle" style="color:var(--text-muted-strong);"></i> остановлено';
            document.getElementById('btn-imit-start').style.display = '';
            document.getElementById('btn-imit-stop').style.display = 'none';
        }
        if (d.counts) {
            counters.style.display = '';
            counters.innerHTML =
                '<i class="bi bi-eye"></i> Прочитано: <b>' + (d.counts.reads || 0) + '</b> &nbsp;' +
                '<i class="bi bi-person"></i> Профилей: <b>' + (d.counts.profiles || 0) + '</b> &nbsp;' +
                '<i class="bi bi-image"></i> Фото: <b>' + (d.counts.photos || 0) + '</b> &nbsp;' +
                '<i class="bi bi-arrow-repeat"></i> Раундов: <b>' + (d.counts.rounds || 0) + '</b>';
        }
        if (d.log && d.log.length) {
            log.style.display = '';
            log.innerHTML = d.log.slice().reverse().map(escHtml).join('<br>');
        }
    }).catch(function() {});
}


// ══════════════════════════════════════════════════════════════════
//  Массовая привязка прокси
// ══════════════════════════════════════════════════════════════════

function bulkBindProxies() {
    var target = document.getElementById('bulk-bind-target').value;
    var mode = document.getElementById('bulk-bind-mode').value;
    var resEl = document.getElementById('bulk-bind-result');
    if (resEl) resEl.textContent = 'Привязываю...';

    API.post('/api/proxy/bulk_bind', { target: target, mode: mode }).then(function(d) {
        if (d && d.ok) {
            var msg = 'Привязано ' + d.bound + ' / ' + d.total_accs + ' аккаунтов (' + d.proxies + ' прокси)';
            if (resEl) { resEl.style.color = 'var(--success)'; resEl.textContent = msg; }
            showToast(msg, 'success');
            if (typeof loadAccounts === 'function') loadAccounts();
        } else {
            if (resEl) { resEl.style.color = 'var(--danger)'; resEl.textContent = (d && d.error) || 'Ошибка'; }
            showToast((d && d.error) || 'Ошибка привязки', 'error');
        }
    }).catch(function(e) {
        if (resEl) { resEl.style.color = 'var(--danger)'; resEl.textContent = 'Ошибка: ' + (e && e.error || ''); }
        showToast('Ошибка', 'error');
    });
}

function bulkUnbindProxies() {
    if (!confirm('Снять прокси со ВСЕХ аккаунтов? Без прокси они рискуют поймать бан.')) return;
    var resEl = document.getElementById('bulk-bind-result');
    if (resEl) resEl.textContent = 'Снимаю...';

    API.post('/api/proxy/bulk_unbind', {}).then(function(d) {
        if (d && d.ok) {
            var msg = 'Снято с ' + d.unbound + ' аккаунтов';
            if (resEl) { resEl.style.color = 'var(--warning)'; resEl.textContent = msg; }
            showToast(msg, 'success');
            if (typeof loadAccounts === 'function') loadAccounts();
        }
    }).catch(function() { showToast('Ошибка', 'error'); });
}


// ══════════════════════════════════════════════════════════════════
//  Расписание / рабочие часы
// ══════════════════════════════════════════════════════════════════

function loadSchedule() {
    API.get('/api/schedule/get').then(function(d) {
        var s = (d && d.schedule) || {};
        var en = document.getElementById('schedule-enabled');
        var f = document.getElementById('schedule-from');
        var t = document.getElementById('schedule-to');
        if (en) en.checked = !!s.enabled;
        if (f && s.from !== undefined) f.value = s.from;
        if (t && s.to !== undefined) t.value = s.to;
        var nowEl = document.getElementById('schedule-now');
        if (nowEl) {
            var nd = new Date();
            nowEl.textContent = nd.toLocaleTimeString('ru-RU', {hour: '2-digit', minute: '2-digit'});
        }
    }).catch(function() {});
}

function saveSchedule() {
    var en = document.getElementById('schedule-enabled');
    var f = document.getElementById('schedule-from');
    var t = document.getElementById('schedule-to');
    if (!en) return;
    API.post('/api/schedule/save', {
        enabled: en.checked,
        from: parseInt(f.value || '9', 10),
        to: parseInt(t.value || '23', 10)
    }).then(function(d) {
        if (d && d.ok) showToast('Расписание сохранено', 'success');
    }).catch(function() {});
}


// ══════════════════════════════════════════════════════════════════
//  TG-бот уведомления
// ══════════════════════════════════════════════════════════════════

function loadTgBot() {
    API.get('/api/tg_bot/get').then(function(d) {
        var b = (d && d.tg_bot) || {};
        var tk = document.getElementById('tg-bot-token');
        var ch = document.getElementById('tg-bot-chat-id');
        if (tk) tk.value = b.token || '';
        if (ch) ch.value = b.chat_id || '';
        var ids = ['notify_spamblock', 'notify_task_done', 'notify_error'];
        ids.forEach(function(k) {
            var el = document.getElementById('tg-' + k.replace('_', '-'));
            if (el) el.checked = !!b[k];
        });
        var st = document.getElementById('tg-bot-status');
        if (st) {
            if (b.token && b.chat_id) {
                st.innerHTML = '<i class="bi bi-check-circle-fill" style="color:var(--success);"></i> настроен';
            } else {
                st.innerHTML = '<i class="bi bi-circle" style="color:var(--text-muted-strong);"></i> не настроен';
            }
        }
    }).catch(function() {});
}

function saveTgBot() {
    var payload = {
        token: document.getElementById('tg-bot-token').value.trim(),
        chat_id: document.getElementById('tg-bot-chat-id').value.trim(),
        notify_spamblock: document.getElementById('tg-notify-spamblock').checked,
        notify_task_done: document.getElementById('tg-notify-task-done').checked,
        notify_error: document.getElementById('tg-notify-error').checked,
    };
    API.post('/api/tg_bot/save', payload).then(function(d) {
        if (d && d.ok) {
            showToast('Настройки бота сохранены', 'success');
            loadTgBot();
        }
    }).catch(function() { showToast('Ошибка', 'error'); });
}

function testTgBot() {
    showToast('Отправляю тест...', 'info');
    API.post('/api/tg_bot/test', {}).then(function(d) {
        if (d && d.ok) {
            showToast('Тест отправлен — проверь Telegram!', 'success');
        } else {
            showToast((d && d.error) || 'Не удалось — проверь токен/chat_id', 'error');
        }
    }).catch(function() { showToast('Ошибка', 'error'); });
}


// ══════════════════════════════════════════════════════════════════
//  Массовое заполнение профилей из материалов (bulk fill)
// ══════════════════════════════════════════════════════════════════

var _bfPollTimer = null;

function bfScanMaterials() {
    var folder = ((document.getElementById('bf-materials-path') || {}).value || '').trim();
    if (!folder) { showToast('Укажи путь к папке с материалами', 'warning'); return; }
    var rs = document.getElementById('bf-scan-result');
    if (rs) rs.innerHTML = '<i class="bi bi-arrow-clockwise" style="animation:spin 1s linear infinite;"></i> Сканирую...';
    API.post('/api/profile/bulk_scan', { path: folder }).then(function(d) {
        if (d.error) {
            rs.innerHTML = '<span style="color:var(--danger);">' + escHtml(d.error) + '</span>';
            return;
        }
        var r = d.result || {};
        var lines = [];
        ['female', 'male', 'general'].forEach(function(k) {
            if (!r[k]) return;
            var label = k === 'female' ? 'Женские' : k === 'male' ? 'Мужские' : 'Общая';
            var inf = r[k];
            var bits = [];
            if (inf.names) bits.push('Имён: ' + inf.names);
            if (inf.surnames) bits.push('Фамилий: ' + inf.surnames);
            if (inf.bios) bits.push('Био: ' + inf.bios);
            if (inf.avatars) bits.push('<b style="color:var(--success);">Аватарок: ' + inf.avatars + '</b>');
            if (inf.stories) bits.push('Сторис: ' + inf.stories);
            if (!bits.length) bits.push('<i style="color:var(--text-muted);">пусто</i>');
            lines.push('<div><b>' + label + ':</b> ' + bits.join(' · ') + '</div>');
        });
        rs.innerHTML = lines.length ? lines.join('') : '<span style="color:var(--warning);">Подпапок не найдено. Проверь структуру.</span>';
    }).catch(function() {
        rs.innerHTML = '<span style="color:var(--danger);">Не удалось проверить</span>';
    });
}

function bfOpenMaterials() {
    var folder = ((document.getElementById('bf-materials-path') || {}).value || '').trim();
    if (!folder) { showToast('Укажи путь', 'warning'); return; }
    API.post('/api/profile/bulk_open', { path: folder }).catch(function(){});
}

function bfStart() {
    var folder = ((document.getElementById('bf-materials-path') || {}).value || '').trim();
    if (!folder) { showToast('Укажи путь к папке', 'warning'); return; }

    // Собираем выбранные акки из bulk-fill селектора
    var phones = [];
    document.querySelectorAll('#acc-selector-bulk-fill .acc-chip.is-selected').forEach(function(el) {
        var p = el.getAttribute('data-phone');
        if (p) phones.push(p);
    });
    if (!phones.length) { showToast('Выбери аккаунты', 'warning'); return; }

    var payload = {
        path: folder,
        phones: phones,
        do_name: !!(document.getElementById('bf-do-name') || {}).checked,
        do_bio: !!(document.getElementById('bf-do-bio') || {}).checked,
        do_avatar: !!(document.getElementById('bf-do-avatar') || {}).checked,
        do_stories: !!(document.getElementById('bf-do-stories') || {}).checked,
        avatar_once: !!(document.getElementById('bf-avatar-once') || {}).checked,
        delete_old: !!(document.getElementById('bf-delete-old') || {}).checked,
        move_processed: !!(document.getElementById('bf-move-processed') || {}).checked,
        target_folder: ((document.getElementById('bf-target-folder') || {}).value || '').trim(),
    };

    showToast('Запускаю заполнение...', 'info');
    API.post('/api/profile/bulk_start', payload).then(function(d) {
        if (d.ok) {
            document.getElementById('btn-bf-start').style.display = 'none';
            document.getElementById('btn-bf-stop').style.display = 'inline-flex';
            document.getElementById('bf-progress').style.display = 'block';
            document.getElementById('bf-log').style.display = 'block';
            if (_bfPollTimer) clearInterval(_bfPollTimer);
            _bfPollTimer = setInterval(bfPollStatus, 1500);
        } else {
            showToast(d.error || 'Не удалось запустить', 'error');
        }
    }).catch(function() { showToast('Ошибка', 'error'); });
}

function bfStop() {
    API.post('/api/profile/bulk_stop', {}).then(function() { showToast('Останавливаю...', 'info'); });
}

function bfPollStatus() {
    API.get('/api/profile/bulk_status').then(function(d) {
        if (!d) return;
        var bar = document.getElementById('bf-progress-bar');
        var txt = document.getElementById('bf-progress-text');
        var log = document.getElementById('bf-log');
        var lbl = document.getElementById('bf-status-label');
        var total = d.total || 0, done = d.done || 0;
        if (bar) bar.style.width = (total > 0 ? Math.round(done/total*100) : 0) + '%';
        if (txt) txt.innerHTML = '<b>' + done + '</b> / ' + total
            + '  ·  ✓ <span style="color:var(--success);">' + (d.ok || 0) + '</span>'
            + '  ·  ✗ <span style="color:var(--danger);">' + (d.fail || 0) + '</span>';
        if (log && d.logs) {
            log.innerHTML = (d.logs || []).map(function(l){
                var color = 'var(--text-muted)';
                if (l.indexOf('✓') !== -1) color = 'var(--success)';
                else if (l.indexOf('✗') !== -1) color = 'var(--danger)';
                else if (l.indexOf('⚠') !== -1 || l.indexOf('↪') !== -1) color = 'var(--warning)';
                return '<div style="color:'+color+';">'+escHtml(l)+'</div>';
            }).join('');
            log.scrollTop = log.scrollHeight;
        }
        if (lbl) {
            if (d.running) lbl.textContent = '⏳ работа...';
            else if (d.status === 'finished') lbl.textContent = '✓ завершено';
            else if (d.status === 'stopped') lbl.textContent = 'остановлено';
        }
        if (!d.running) {
            if (_bfPollTimer) { clearInterval(_bfPollTimer); _bfPollTimer = null; }
            document.getElementById('btn-bf-start').style.display = 'inline-flex';
            document.getElementById('btn-bf-stop').style.display = 'none';
        }
    }).catch(function(){});
}


// ══════════════════════════════════════════════════════════════════
//  Чекер аккаунтов
// ══════════════════════════════════════════════════════════════════

var _ckPollTimer = null;
var _ckSpamTexts = [];

function loadCheckerConfig() {
    API.get('/api/checker/config').then(function(d) {
        var c = (d && d.checker) || {};
        var set = function(id, val) { var el = document.getElementById(id); if (el) { if (el.type === 'checkbox') el.checked = !!val; else el.value = val || ''; } };
        set('ck-rucaptcha', c.rucaptcha_key);
        set('ck-deepseek', c.deepseek_key);
        set('ck-freeze-prompt', c.freeze_prompt);
        set('ck-check-spam', c.check_spamblock !== false);
        set('ck-submit-spam-appeal', c.submit_spamblock_appeal);
        set('ck-submit-freeze-appeal', c.submit_freeze_appeal);
        set('ck-use-hacked', c.use_hacked_reason !== false);
        set('ck-use-mistake', c.use_mistake_reason !== false);
        set('ck-sort-folders', c.sort_by_folders !== false);
        set('ck-rename-files', c.rename_files);
        set('ck-delete-dialog', c.delete_spambot_dialog);
        set('ck-folder-alive', c.folder_alive || 'Аккаунты/Живые');
        set('ck-folder-temp', c.folder_temp_spam || 'Аккаунты/Спамблок/Временный');
        set('ck-folder-eternal', c.folder_eternal_spam || 'Аккаунты/Спамблок/Вечный');
        set('ck-folder-frozen', c.folder_frozen || 'Аккаунты/Замороженные');
        _ckSpamTexts = c.spamblock_texts || [];
    }).catch(function(){});
}

function saveCheckerConfig() {
    var get = function(id) { var el = document.getElementById(id); if (!el) return ''; return el.type === 'checkbox' ? el.checked : el.value; };
    var payload = {
        rucaptcha_key: get('ck-rucaptcha'),
        deepseek_key: get('ck-deepseek'),
        freeze_prompt: get('ck-freeze-prompt'),
        check_spamblock: get('ck-check-spam'),
        submit_spamblock_appeal: get('ck-submit-spam-appeal'),
        submit_freeze_appeal: get('ck-submit-freeze-appeal'),
        use_hacked_reason: get('ck-use-hacked'),
        use_mistake_reason: get('ck-use-mistake'),
        sort_by_folders: get('ck-sort-folders'),
        rename_files: get('ck-rename-files'),
        delete_spambot_dialog: get('ck-delete-dialog'),
        folder_alive: get('ck-folder-alive'),
        folder_temp_spam: get('ck-folder-temp'),
        folder_eternal_spam: get('ck-folder-eternal'),
        folder_frozen: get('ck-folder-frozen'),
        spamblock_texts: _ckSpamTexts,
    };
    API.post('/api/checker/config', payload).then(function(d) {
        if (d && d.ok) showToast('Настройки сохранены', 'success');
    }).catch(function() { showToast('Ошибка', 'error'); });
}

function ckOpenSpamTexts() {
    var existing = (_ckSpamTexts || []).join('\n');
    var overlay = document.createElement('div');
    overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.65);z-index:9999;display:flex;align-items:center;justify-content:center;padding:20px;';
    overlay.innerHTML =
        '<div style="background:var(--bg-card);border:1px solid var(--border);border-radius:14px;padding:20px;width:780px;max-width:96vw;max-height:90vh;display:flex;flex-direction:column;box-shadow:var(--shadow-lg);">' +
        '<div style="display:flex;align-items:center;margin-bottom:10px;"><h3 style="margin:0;font-size:1rem;color:var(--text-primary);">Тексты для снятия спамблока</h3>' +
        '<button onclick="this.closest(\'.ck-modal\').remove()" style="margin-left:auto;background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:1.2rem;">×</button></div>' +
        '<div class="info-box" style="margin-bottom:10px;font-size:0.7rem;">Запиши каждый текст с новой строчки. Эти тексты будут отправляться в @SpamBot, чтобы попросить снять спамблок с аккаунта.</div>' +
        '<textarea id="ck-spam-texts-area" style="flex:1;min-height:300px;background:var(--bg-input);border:1px solid var(--border);border-radius:8px;color:var(--text-primary);padding:10px;font-family:monospace;font-size:0.78rem;resize:vertical;">' + escHtml(existing) + '</textarea>' +
        '<div style="display:flex;justify-content:space-between;align-items:center;margin-top:12px;">' +
        '<span style="font-size:0.72rem;color:var(--text-muted);" id="ck-spam-count">Строк: ' + _ckSpamTexts.length + '</span>' +
        '<button class="btn btn-accent" onclick="ckSaveSpamTexts(this)"><i class="bi bi-save"></i> Сохранить</button>' +
        '</div></div>';
    overlay.className = 'ck-modal';
    overlay.onclick = function(e){ if (e.target === overlay) overlay.remove(); };
    document.body.appendChild(overlay);
    var ta = document.getElementById('ck-spam-texts-area');
    if (ta) {
        ta.addEventListener('input', function() {
            var lines = ta.value.split('\n').filter(function(s){return s.trim();});
            var c = document.getElementById('ck-spam-count');
            if (c) c.textContent = 'Строк: ' + lines.length;
        });
    }
}

function ckSaveSpamTexts(btn) {
    var ta = document.getElementById('ck-spam-texts-area');
    if (!ta) return;
    _ckSpamTexts = ta.value.split('\n').map(function(s){return s.trim();}).filter(Boolean);
    btn.closest('.ck-modal').remove();
    showToast('Сохранено: ' + _ckSpamTexts.length + ' строк', 'success');
}

function startChecker() {
    var phones = [];
    document.querySelectorAll('#acc-selector-checker .acc-chip.is-selected').forEach(function(el) {
        var p = el.getAttribute('data-phone');
        if (p) phones.push(p);
    });
    if (!phones.length) { showToast('Выбери аккаунты', 'warning'); return; }

    // Сначала сохраняем настройки чтобы runtime их видел
    saveCheckerConfig();
    setTimeout(function() {
        API.post('/api/checker/start', { phones: phones }).then(function(d) {
            if (d.ok) {
                document.getElementById('btn-ck-start').style.display = 'none';
                document.getElementById('btn-ck-stop').style.display = 'inline-flex';
                document.getElementById('ck-progress').style.display = 'block';
                document.getElementById('ck-log').style.display = 'block';
                if (_ckPollTimer) clearInterval(_ckPollTimer);
                _ckPollTimer = setInterval(pollCheckerStatus, 2000);
            } else {
                showToast(d.error || 'Не удалось запустить', 'error');
            }
        }).catch(function() { showToast('Ошибка', 'error'); });
    }, 300);
}

function stopChecker() {
    API.post('/api/checker/stop', {}).then(function() { showToast('Останавливаю...', 'info'); });
}

function pollCheckerStatus() {
    API.get('/api/checker/status').then(function(d) {
        if (!d) return;
        var bar = document.getElementById('ck-progress-bar');
        var txt = document.getElementById('ck-progress-text');
        var cnt = document.getElementById('ck-counters');
        var log = document.getElementById('ck-log');
        var lbl = document.getElementById('ck-status-label');
        var total = d.total || 0, done = d.done || 0;
        if (bar) bar.style.width = (total > 0 ? Math.round(done/total*100) : 0) + '%';
        if (txt) txt.innerHTML = '<b>' + done + '</b> / ' + total;
        if (cnt) cnt.innerHTML =
            '<span><span style="color:var(--success);">✓ Живых:</span> <b>' + (d.alive||0) + '</b></span>' +
            '<span><span style="color:var(--warning);">⏳ Врем.спам:</span> <b>' + (d.temp_spam||0) + '</b></span>' +
            '<span><span style="color:var(--danger);">✗ Вечн.спам:</span> <b>' + (d.eternal_spam||0) + '</b></span>' +
            '<span><span style="color:var(--info);">❄ Замор.:</span> <b>' + (d.frozen||0) + '</b></span>' +
            '<span><span style="color:var(--text-muted);">⚠ Ошибок:</span> <b>' + (d.error||0) + '</b></span>';
        if (log && d.logs) {
            log.innerHTML = (d.logs || []).map(function(l){
                var color = 'var(--text-muted)';
                if (l.indexOf('✓') !== -1) color = 'var(--success)';
                else if (l.indexOf('⏳') !== -1) color = 'var(--warning)';
                else if (l.indexOf('✗') !== -1) color = 'var(--danger)';
                else if (l.indexOf('❄') !== -1) color = 'var(--info)';
                else if (l.indexOf('⚠') !== -1 || l.indexOf('↪') !== -1) color = 'var(--accent-light)';
                return '<div style="color:'+color+';">'+escHtml(l)+'</div>';
            }).join('');
            log.scrollTop = log.scrollHeight;
        }
        if (lbl) {
            if (d.running) lbl.textContent = '⏳ работа...';
            else if (d.status === 'finished') lbl.textContent = '✓ завершено';
            else if (d.status === 'stopped') lbl.textContent = 'остановлено';
        }
        if (!d.running) {
            if (_ckPollTimer) { clearInterval(_ckPollTimer); _ckPollTimer = null; }
            document.getElementById('btn-ck-start').style.display = 'inline-flex';
            document.getElementById('btn-ck-stop').style.display = 'none';
        }
    }).catch(function(){});
}


// ══════════════════════════════════════════════════════════════════
//  Накрутка (boost)
// ══════════════════════════════════════════════════════════════════

var _boostPollTimer = null;

function onBoostTypeChange() {
    var t = (document.getElementById('boost-type') || {}).value || 'subscribers';
    var labelMap = {
        subscribers: 'Ссылка на канал / @username (подписаться сюда)',
        reactions:   'Ссылка на пост (t.me/channel/123)',
        polls:       'Ссылка на пост-опрос',
        buttons:     'Ссылка на сообщение с inline-кнопками',
        bots:        '@username бота для запуска',
        views:       'Ссылка на пост (накрутить просмотры)',
    };
    var lbl = document.getElementById('boost-target-label');
    if (lbl) lbl.textContent = labelMap[t] || 'Цель';

    var fields = {
        'boost-field-emojis': t === 'reactions',
        'boost-field-poll':   t === 'polls',
        'boost-field-button': t === 'buttons',
    };
    Object.keys(fields).forEach(function(id) {
        var el = document.getElementById(id);
        if (el) el.style.display = fields[id] ? 'block' : 'none';
    });
}

function _parseDelayRange(text) {
    var s = (text || '').replace(/\s/g, '');
    var m = s.match(/^([\d.]+)(?:-([\d.]+))?$/);
    if (!m) return [5, 15];
    var lo = parseFloat(m[1]) || 5;
    var hi = m[2] ? parseFloat(m[2]) : lo;
    if (hi < lo) hi = lo;
    return [lo, hi];
}

function startBoost() {
    var type = (document.getElementById('boost-type') || {}).value || 'subscribers';
    var target = ((document.getElementById('boost-target') || {}).value || '').trim();
    if (!target) { showToast('Укажи цель (ссылку/юзернейм)', 'warning'); return; }

    var delay = _parseDelayRange((document.getElementById('boost-delay') || {}).value || '5-15');
    var emojisStr = ((document.getElementById('boost-emojis') || {}).value || '').trim();
    var emojis = emojisStr ? emojisStr.split(/\s+/).filter(Boolean) : [];

    var pollOptionRaw = (document.getElementById('boost-poll-option') || {}).value;
    var pollOption = (pollOptionRaw === '' || pollOptionRaw == null) ? null : parseInt(pollOptionRaw);

    var payload = {
        type: type,
        target: target,
        count: parseInt((document.getElementById('boost-count') || {}).value) || 50,
        delay_min: delay[0],
        delay_max: delay[1],
        threads: parseInt((document.getElementById('boost-threads') || {}).value) || 1,
        folder: ((document.getElementById('boost-folder') || {}).value || '').trim(),
        emojis: emojis,
        poll_option: pollOption,
        button_text: ((document.getElementById('boost-button') || {}).value || '').trim(),
        allow_restart: !!(document.getElementById('boost-allow-restart') || {}).checked,
        allow_revote: !!(document.getElementById('boost-allow-revote') || {}).checked,
    };

    // Soft-warning для большого числа акков на 1 цель
    if (payload.count > 50) {
        if (!confirm('Накрутка >50 аккаунтов за один заход на одну цель. Telegram может пометить цель как накрученную и применить санкции (для канала — пометка «массовый набор», скрытие из поиска). Продолжить?')) return;
    }

    showToast('Запускаю накрутку...', 'info');
    API.post('/api/boost/start', payload).then(function(d) {
        if (d.ok) {
            document.getElementById('btn-boost-start').style.display = 'none';
            document.getElementById('btn-boost-stop').style.display = 'inline-flex';
            document.getElementById('boost-progress').style.display = 'block';
            document.getElementById('boost-log').style.display = 'block';
            if (_boostPollTimer) clearInterval(_boostPollTimer);
            _boostPollTimer = setInterval(pollBoostStatus, 1500);
        } else {
            showToast(d.error || 'Не удалось запустить', 'error');
        }
    }).catch(function() { showToast('Ошибка', 'error'); });
}

function stopBoost() {
    API.post('/api/boost/stop', {}).then(function() {
        showToast('Останавливаю...', 'info');
    });
}

function pollBoostStatus() {
    API.get('/api/boost/status').then(function(d) {
        if (!d) return;
        var bar = document.getElementById('boost-progress-bar');
        var txt = document.getElementById('boost-progress-text');
        var log = document.getElementById('boost-log');
        var lbl = document.getElementById('boost-status-label');
        var total = d.total || 0, done = d.done || 0;
        if (bar) bar.style.width = (total > 0 ? Math.round(done/total*100) : 0) + '%';
        if (txt) txt.innerHTML = '<b>' + done + '</b> / ' + total
            + '  ·  ✓ <span style="color:var(--success);">' + (d.ok || 0) + '</span>'
            + '  ·  ✗ <span style="color:var(--danger);">' + (d.fail || 0) + '</span>';
        if (log && d.logs) {
            log.innerHTML = (d.logs || []).map(function(l){
                var color = 'var(--text-muted)';
                if (l.indexOf('✓') !== -1) color = 'var(--success)';
                else if (l.indexOf('✗') !== -1) color = 'var(--danger)';
                else if (l.indexOf('⚠') !== -1 || l.indexOf('❌') !== -1) color = 'var(--warning)';
                return '<div style="color:'+color+';">'+escHtml(l)+'</div>';
            }).join('');
            log.scrollTop = log.scrollHeight;
        }
        if (lbl) {
            if (d.running) lbl.textContent = '⏳ работа...';
            else if (d.status === 'finished') lbl.textContent = '✓ завершено';
            else if (d.status === 'stopped') lbl.textContent = 'остановлено';
        }
        if (!d.running) {
            if (_boostPollTimer) { clearInterval(_boostPollTimer); _boostPollTimer = null; }
            document.getElementById('btn-boost-start').style.display = 'inline-flex';
            document.getElementById('btn-boost-stop').style.display = 'none';
        }
    }).catch(function(){});
}


// ══════════════════════════════════════════════════════════════════
//  Тема оформления
// ══════════════════════════════════════════════════════════════════

function applyTheme(theme) {
    var html = document.documentElement;
    var body = document.body;
    ['theme-calm', 'theme-amoled', 'theme-light'].forEach(function(c) {
        html.classList.remove(c);
        body.classList.remove(c);
    });
    if (theme === 'amoled' || theme === 'light') {
        html.classList.add('theme-' + theme);
        body.classList.add('theme-' + theme);
    }
    // Подсветить активную карточку в picker'е
    document.querySelectorAll('.theme-card').forEach(function(c) {
        c.classList.toggle('is-active', c.getAttribute('data-theme') === theme);
    });
}

function selectTheme(theme) {
    if (['calm', 'amoled', 'light'].indexOf(theme) === -1) return;
    applyTheme(theme);
    try { localStorage.setItem('tp_theme', theme); } catch(e) {}
    API.post('/api/theme/save', { theme: theme }).catch(function() {});
}

function loadTheme() {
    // Сначала из localStorage (мгновенно), потом синк с сервера
    var saved = 'calm';
    try { saved = localStorage.getItem('tp_theme') || 'calm'; } catch(e) {}
    applyTheme(saved);
    API.get('/api/theme/get').then(function(d) {
        var srv = (d && d.theme) || 'calm';
        if (srv !== saved) {
            applyTheme(srv);
            try { localStorage.setItem('tp_theme', srv); } catch(e) {}
        }
    }).catch(function() {});
}


// ══════════════════════════════════════════════════════════════════
//  Anti-detect статистика (на вкладке Аналитика)
// ══════════════════════════════════════════════════════════════════

function loadAdStats() {
    API.get('/api/ad_stats').then(function(d) {
        var c = (d && d.counters) || {};
        function set(id, v) {
            var el = document.getElementById(id);
            if (el) el.textContent = (v || 0).toLocaleString('ru-RU');
        }
        set('ad-stat-precheck-runs', c.pre_check_runs);
        set('ad-stat-precheck-blocked', c.pre_check_blocked);
        set('ad-stat-captcha', c.captcha_solved);
        set('ad-stat-profiles', c.profiles_viewed);
        set('ad-stat-photos', c.photos_viewed);
        set('ad-stat-reads', c.messages_read);
    }).catch(function() {});
}
