// UI рассылки (вёрстка/логика как undergroundbrand.ru) → только локальный /api SPAMIMPAKTER
const API_URL = window.API_URL || '/api';
let broadcastContentType = 'text';
let broadcastSource = 'parsed';
let broadcastFileUsers = null;
let bcPhonesMode = 'text';
let broadcastPhonesFileUsers = null;
let editingBroadcastJobId = null;
let editingOriginalParsedBaseId = null;
let editingOriginalSource = null;
let editingHasAutoreplyRules = false;
let editingInitialAutoreplyEnabled = null;
let editingInitialAutoreplyTemplateId = null;
let editingPrefAutoreplyTemplateId = null;
let bcAccountPicker = null;
let _broadcastJobsReqId = 0;
let _bcAllJobs = [];
let currentBroadcastJobId = null;
let broadcastDetailRefresh = null;
let _broadcastListRefresh = null;
let _originalText = '';
let _bcAiSpintax = '';
let _bcAiVariants = [];
let _bcSelectedVariantKeys = new Set();

function openJobAutoreplyFromModal() {
    if (typeof window.openJobAutoreplySetup === 'function') {
        window.openJobAutoreplySetup(editingBroadcastJobId);
    }
}
window.openJobAutoreplyFromModal = openJobAutoreplyFromModal;

async function showCreateBroadcastModal(isEdit = false) {
    try {
    if (window.__ugModalsReady) await window.__ugModalsReady;
    const modal = document.getElementById('createBroadcastModal');
    if (!modal) { showNotification('Модалка не загрузилась — Ctrl+F5', 'error'); return; }
    const title = modal.querySelector('h2');
    const submitBtn = document.getElementById('bcSubmitBtn');
    
    if (isEdit && editingBroadcastJobId) {
        title.textContent = '✏️ Редактировать рассылку';
        submitBtn.textContent = '💾 Сохранить изменения';
        submitBtn.setAttribute('onclick', 'saveBroadcastJob()');
    } else {
        editingBroadcastJobId = null;
        editingOriginalParsedBaseId = null;
        editingHasAutoreplyRules = false;
        editingInitialAutoreplyEnabled = null;
        editingInitialAutoreplyTemplateId = null;
        editingPrefAutoreplyTemplateId = null;
        broadcastFileUsers = null;
        broadcastPhonesFileUsers = null;
        title.textContent = '📢 Создать рассылку';
        submitBtn.textContent = '🚀 Создать задачу рассылки';
        submitBtn.setAttribute('onclick', 'createBroadcastJob()');
        // Очищаем все поля при создании новой
        document.getElementById('bcName').value = '';
        document.getElementById('bcMessageText').value = '';
        _bcAiSpintax = '';
        _bcAiVariants = [];
        _bcSelectedVariantKeys = new Set();
        _originalText = '';
        document.getElementById('bcPostLinksContainer').innerHTML = '';
        addPostLinkRow();
        document.getElementById('bcDeleteAfterSend').checked = false;
        if (document.getElementById('bcAutoreplyEnabled')) document.getElementById('bcAutoreplyEnabled').checked = false;
        if (document.getElementById('bcAutoreplyTpl')) document.getElementById('bcAutoreplyTpl').value = '';
        document.getElementById('bcSilentSend').checked = false;
        document.getElementById('bcHideAuthor').checked = false;
        document.getElementById('bcPauseMin').value = '15';
        document.getElementById('bcPauseMax').value = '30';
        document.getElementById('bcMsgPerAccount').value = '0';
        if (document.getElementById('bcParallel')) document.getElementById('bcParallel').value = '10';
        document.getElementById('bcDailyLimit').value = '0';
        document.getElementById('bcAutoStop').value = '0';
        document.getElementById('bcParsedBase').value = '';
        document.getElementById('bcManualUsers').value = '';
        if (document.getElementById('bcPhonesManual')) document.getElementById('bcPhonesManual').value = '';
        if (document.getElementById('bcPhonesFileInput')) document.getElementById('bcPhonesFileInput').value = '';
        if (document.getElementById('bcPhonesFileInfo')) document.getElementById('bcPhonesFileInfo').textContent = '';
        if (document.getElementById('bcPhonesManualCount')) document.getElementById('bcPhonesManualCount').textContent = '';
        setBroadcastContentType('text');
        setBroadcastSource('parsed');
    }
    
    const arSetupBtn = document.getElementById('bcAutoreplySetupBtn');
    if (arSetupBtn) arSetupBtn.hidden = !(isEdit && editingBroadcastJobId);
    modal.classList.add('show');
    document.body.classList.add('ug-bc-modal-open');
    if (!isEdit && document.getElementById('bcParallel')) {
        try {
            const sres = await fetch(`${API_URL}/settings`, {
                headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
            });
            if (sres.ok) {
                const s = await sres.json();
                document.getElementById('bcParallel').value = s.parallel_workers || '10';
            }
        } catch (_) {}
    }
    loadBroadcastParserBases();
    loadBroadcastAccounts();
    loadBcAutoreplyTemplates(editingPrefAutoreplyTemplateId);
    refreshBcAiProfiles();
    await syncBcAiFromSettings();
    loadBcAiPromptFromStorage();
    loadBcAiVariantCount();
    } catch (e) { console.error(e); showNotification(e.message || 'Ошибка формы', 'error'); }
}

function _bcPickTemplateForEdit(job) {
    const explicit = Number(job?.autoreply_template_id || 0) || null;
    if (explicit) return explicit;
    const rules = Array.isArray(job?.autoreply_rules) ? job.autoreply_rules : [];
    const enabled = rules.filter(r => Number(r?.enabled || 0) === 1);
    const startKey = String(job?.funnel_start_status || 'new').trim().toLowerCase() || 'new';
    const byStart = enabled.find(r => String(r?.trigger_status || '').trim().toLowerCase() === startKey);
    if (byStart?.template_id) return Number(byStart.template_id) || null;
    const byNew = enabled.find(r => String(r?.trigger_status || '').trim().toLowerCase() === 'new');
    if (byNew?.template_id) return Number(byNew.template_id) || null;
    if (enabled[0]?.template_id) return Number(enabled[0].template_id) || null;
    return null;
}

async function editBroadcastJob(jobId) {
    if (window.__ugModalsReady) await window.__ugModalsReady;
    try {
        const res = await fetch(`${API_URL}/broadcast/jobs/${jobId}`, {
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (!res.ok) throw new Error('Ошибка загрузки задачи');
        const data = await res.json();
        const job = data.job;
        const rules = Array.isArray(job.autoreply_rules) ? job.autoreply_rules : [];
        const hasEnabledRules = rules.some(r => Number(r?.enabled || 0) === 1);
        editingHasAutoreplyRules = hasEnabledRules;
        editingInitialAutoreplyEnabled = !!job.autoreply_enabled;
        editingInitialAutoreplyTemplateId = Number(job.autoreply_template_id || 0) || null;
        editingPrefAutoreplyTemplateId = _bcPickTemplateForEdit(job);
        
        if (job.status === 'running') {
            showNotification('Нельзя редактировать запущенную задачу. Сначала поставьте на паузу.', 'error');
            return;
        }
        
        editingBroadcastJobId = jobId;
        broadcastFileUsers = null; // Сбрасываем файл от предыдущих операций
        broadcastPhonesFileUsers = null; // Сбрасываем phones-файл от предыдущих операций
        editingOriginalParsedBaseId = job.parsed_base_id || null;
        
        // Очищаем UI от предыдущих файлов
        const fileInfo = document.getElementById('bcFileInfo');
        if (fileInfo) fileInfo.textContent = '';
        const fileInput = document.getElementById('bcFileInput');
        if (fileInput) fileInput.value = '';
        const phonesFileInfo = document.getElementById('bcPhonesFileInfo');
        if (phonesFileInfo) phonesFileInfo.textContent = '';
        const phonesFileInput = document.getElementById('bcPhonesFileInput');
        if (phonesFileInput) phonesFileInput.value = '';
        
        // Сбрасываем _originalText чтобы кнопка "Оригинал" работала корректно
        const savedText = (job.message_text || job.text || '').trim();
        _originalText = savedText;
        _bcAiSpintax = '';
        _bcAiVariants = [];
        _bcSelectedVariantKeys = new Set();
        
        // Заполняем поля
        document.getElementById('bcName').value = job.name || '';
        document.getElementById('bcMessageText').value = savedText;
        _hydrateBcVariantsFromSavedText(savedText);
        // Populate post links
        const plContainer = document.getElementById('bcPostLinksContainer');
        plContainer.innerHTML = '';
        let postLinksArr = job.post_links;
        if (typeof postLinksArr === 'string') { try { postLinksArr = JSON.parse(postLinksArr); } catch(e) { postLinksArr = null; } }
        if (Array.isArray(postLinksArr) && postLinksArr.length > 0) {
            postLinksArr.forEach(pl => addPostLinkRow(pl.url, pl.limit));
        } else if (job.post_link) {
            addPostLinkRow(job.post_link, 0);
        } else {
            addPostLinkRow();
        }
        document.getElementById('bcDeleteAfterSend').checked = job.delete_after_send === true;
        if (document.getElementById('bcAutoreplyEnabled')) {
            document.getElementById('bcAutoreplyEnabled').checked = !!job.autoreply_enabled;
        }
        const arTplSel = document.getElementById('bcAutoreplyTpl');
        if (arTplSel && editingPrefAutoreplyTemplateId) {
            arTplSel.value = String(editingPrefAutoreplyTemplateId);
        }
        const arHint = document.getElementById('bcAutoreplyHint');
        if (arHint && hasEnabledRules) {
            const startKey = String(job.funnel_start_status || 'new').trim().toLowerCase() || 'new';
            const startRule = rules.find(r =>
                Number(r?.enabled || 0) === 1
                && String(r?.trigger_status || '').trim().toLowerCase() === startKey
            );
            const startTpl = startRule?.template_title || (startRule?.template_id ? `AI #${startRule.template_id}` : '—');
            arHint.textContent =
                `Воронка настроена в конструкторе. Старт: «${startKey}» → ${startTpl}. `
                + 'Dropdown ниже = тот же стартовый пресет (legacy). Меняй цепочку в конструкторе.';
        }
        document.getElementById('bcSilentSend').checked = job.silent_send === true;
        document.getElementById('bcHideAuthor').checked = job.hide_author === true;
        document.getElementById('bcPauseMin').value = job.pause_min != null ? job.pause_min : '15';
        document.getElementById('bcPauseMax').value = job.pause_max != null ? job.pause_max : '30';
        const mpaEl = document.getElementById('bcMsgPerAccount');
        if (mpaEl) mpaEl.value = job.messages_per_account != null ? job.messages_per_account : '0';
        if (document.getElementById('bcParallel')) document.getElementById('bcParallel').value = job.parallel_workers != null ? job.parallel_workers : '10';
        document.getElementById('bcDailyLimit').value = job.daily_limit != null ? job.daily_limit : '0';
        document.getElementById('bcAutoStop').value = job.auto_stop_percent != null ? job.auto_stop_percent : '50';
        
        // Тип контента
        setBroadcastContentType(job.content_type || 'text');
        
        // Источник получателей
        const qs = data.queue_stats;
        const recipientInfo = `Текущее состояние: ${qs.pending} в очереди, ${qs.sent} отправлено, ${qs.failed} ошибок, ${qs.skipped} пропущено`;
        if (job.users_source === 'parsed_base' && job.parsed_base_id) {
            setBroadcastSource('parsed');
            editingOriginalSource = 'parsed';
        } else if (job.users_source === 'phones') {
            setBroadcastSource('phones');
            setBcPhonesMode('text');
            document.getElementById('bcPhonesManual').value = `(номера уже загружены — ${recipientInfo})\n(оставьте как есть или введите новых)`;
            editingOriginalSource = 'phones';
        } else {
            setBroadcastSource('manual');
            document.getElementById('bcManualUsers').value = `(получатели уже загружены — ${recipientInfo})\n(оставьте поле как есть или введите новых ниже)`;
            editingOriginalSource = 'manual';
        }
        
        // Открываем модал — это вызовет loadBroadcastParserBases и loadBroadcastAccounts
        showCreateBroadcastModal(true);
        
        // Выбираем аккаунты и базу после загрузки
        const accountIds = job.account_ids || [];
        const parsedBaseId = job.parsed_base_id;
        
        // Ждём загрузки AccountPicker, потом выставляем значения
        let waitAttempts = 0;
        const waitAndSelect = () => {
            waitAttempts++;
            if ((!bcAccountPicker || bcAccountPicker.accounts.length === 0) && waitAttempts < 30) {
                setTimeout(waitAndSelect, 300);
                return;
            }
            if (bcAccountPicker && accountIds.length) {
                bcAccountPicker.setSelection(new Set(accountIds));
            }
            
            // Выбираем парсерную базу если есть
            if (parsedBaseId) {
                const select = document.getElementById('bcParsedBase');
                if (select) {
                    select.value = parsedBaseId;
                    if (!select.value) {
                        // Опция ещё не загрузилась, ждём
                        setTimeout(() => { select.value = parsedBaseId; }, 500);
                    }
                }
            }
        };
        setTimeout(waitAndSelect, 300);
        
    } catch (e) {
        showNotification(e.message, 'error');
    }
}

async function saveBroadcastJob() {
    if (!editingBroadcastJobId) return;
    
    const contentType = broadcastContentType;
    const messageText = _broadcastTextForSave();

    if (contentType === 'text' && !messageText) {
        showNotification('Укажите текст сообщения', 'error');
        return;
    }
    if (contentType === 'post') {
        const postLinks = getPostLinksData();
        if (!postLinks.length) {
            showNotification('Добавьте хотя бы одну ссылку на пост', 'error');
            return;
        }
    }

    const pauseMin = parseInt(document.getElementById('bcPauseMin').value) || 15;
    const pauseMax = parseInt(document.getElementById('bcPauseMax').value) || 30;
    if (pauseMin > pauseMax) {
        showNotification('Минимальная пауза не может быть больше максимальной', 'error');
        return;
    }

    const accountIds = bcAccountPicker ? Array.from(bcAccountPicker.getSelected()) : [];
    if (accountIds.length === 0) {
        showNotification('Выберите хотя бы один аккаунт', 'error');
        return;
    }

    const body = {
        name: document.getElementById('bcName').value.trim() || null,
        content_type: contentType,
        message_text: contentType === 'text' ? messageText : null,
        spintax_enabled: contentType === 'text' && _broadcastShouldRandomize(messageText),
        delete_after_send: document.getElementById('bcDeleteAfterSend').checked,
        silent_send: document.getElementById('bcSilentSend')?.checked || false,
        hide_author: document.getElementById('bcHideAuthor')?.checked || false,
        pause_min: pauseMin,
        pause_max: pauseMax,
        messages_per_account: parseInt(document.getElementById('bcMsgPerAccount')?.value || '0') || 0,
        batch_size: 1,
        parallel_workers: parseInt(document.getElementById('bcParallel')?.value || '10') || 10,
        daily_limit: parseInt(document.getElementById('bcDailyLimit').value) || 0,
        auto_stop_percent: parseInt(document.getElementById('bcAutoStop').value) || 0,
        account_ids: accountIds,
    };
    const currentArEnabled = document.getElementById('bcAutoreplyEnabled')?.checked || false;
    const currentArTemplateId = _bcAutoreplyTemplateId();
    // Важно: если у задачи уже есть rules из конструктора, не шлём autoreply поля
    // при обычном редактировании (аккаунты/лимиты/текст), чтобы не перезаписать
    // стартовый статус rule ("new") legacy-пресетом из модалки.
    if (!editingHasAutoreplyRules) {
        body.autoreply_enabled = currentArEnabled;
        body.autoreply_template_id = currentArTemplateId;
    } else {
        const arChanged =
            currentArEnabled !== !!editingInitialAutoreplyEnabled
            || (currentArTemplateId || null) !== (editingInitialAutoreplyTemplateId || null);
        if (arChanged) {
            body.autoreply_enabled = currentArEnabled;
            body.autoreply_template_id = currentArTemplateId;
        }
    }

    // Post links
    if (contentType === 'post') {
        const postLinks = getPostLinksData();
        body.post_links = postLinks;
        body.post_link = postLinks.length > 0 ? postLinks[0].url : null;
    } else {
        body.post_link = null;
        body.post_links = null;
    }

    // Получатели - только если явно указаны новые
    if (broadcastSource === 'parsed') {
        const parsedBaseId = parseInt(document.getElementById('bcParsedBase').value);
        if (parsedBaseId) {
            // Отправляем parsed_base_id только если база изменилась (чтобы не сбросить прогресс)
            if (!editingBroadcastJobId || parsedBaseId !== editingOriginalParsedBaseId) {
                body.parsed_base_id = parsedBaseId;
            }
        }
    } else if (broadcastSource === 'file' && broadcastFileUsers && broadcastFileUsers.length > 0) {
        body.users_data = broadcastFileUsers;
    } else if (broadcastSource === 'phones') {
        if (bcPhonesMode === 'file' && broadcastPhonesFileUsers && broadcastPhonesFileUsers.length > 0) {
            body.users_data = broadcastPhonesFileUsers;
        } else if (bcPhonesMode === 'text') {
            let phonesText = document.getElementById('bcPhonesManual').value.trim();
            phonesText = phonesText.split('\n').filter(l => !l.trim().startsWith('(номера уже загружены') && !l.trim().startsWith('(оставьте как есть')).join('\n').trim();
            if (phonesText) {
                const phoneUsers = parsePhoneLines(phonesText);
                if (phoneUsers.length > 0) {
                    body.users_data = phoneUsers;
                }
            }
        }
    } else if (broadcastSource === 'manual') {
        let manualText = document.getElementById('bcManualUsers').value.trim();
        // Убираем строки-плейсхолдеры
        manualText = manualText.split('\n').filter(l => !l.trim().startsWith('(получатели уже загружены') && !l.trim().startsWith('(оставьте поле как есть')).join('\n').trim();
        if (manualText) {
            const users = parseManualUsers(manualText);
            if (users.length > 0) {
                body.users_data = users;
            }
        }
    }

    // Источник получателей + замена при смене режима. Без этого backend ДОБАВЛЯЛ
    // новых к старым и не переключал источник → рассылка шла и по прежней базе.
    const _srcMap = { parsed: 'parsed', phones: 'phones', manual: 'manual', file: 'manual' };
    const mappedSource = _srcMap[broadcastSource] || 'manual';
    body.recipient_source = mappedSource;
    if (editingBroadcastJobId) {
        const _pbid = parseInt(document.getElementById('bcParsedBase').value) || 0;
        const srcChanged = mappedSource !== (editingOriginalSource || 'manual');
        const baseChanged = mappedSource === 'parsed' && _pbid && _pbid !== editingOriginalParsedBaseId;
        if (srcChanged || baseChanged) body.replace_recipients = true;
    }

    try {
        const res = await fetch(`${API_URL}/broadcast/jobs/${editingBroadcastJobId}`, {
            method: 'PUT',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${localStorage.getItem('token')}`
            },
            body: JSON.stringify(body)
        });
        
        const data = await res.json();
        if (!res.ok) throw new Error(data.detail || 'Ошибка обновления');
        
        showNotification('✅ Задача обновлена', 'success');
        closeBroadcastModal();
        loadBroadcastJobs();
        editingBroadcastJobId = null;
    } catch (e) {
        showNotification(e.message, 'error');
    }
}

function parseManualUsers(text) {
    const lines = text.split('\n').filter(l => l.trim());
    return lines.map(line => {
        line = line.trim().replace(/^@/, '');
        return /^\d+$/.test(line) ? { user_id: parseInt(line) } : { username: line };
    });
}

function closeBroadcastModal() {
    document.getElementById('createBroadcastModal')?.classList.remove('show');
    if (!document.getElementById('broadcastDetailsModal')?.classList.contains('show')) {
        document.body.classList.remove('ug-bc-modal-open');
    }
    editingBroadcastJobId = null;
    editingOriginalParsedBaseId = null;
    editingOriginalSource = null;
    editingHasAutoreplyRules = false;
    editingInitialAutoreplyEnabled = null;
    editingInitialAutoreplyTemplateId = null;
    editingPrefAutoreplyTemplateId = null;
    broadcastFileUsers = null;
    if (bcAccountPicker) { bcAccountPicker.destroy(); bcAccountPicker = null; }
}

async function requeueProcessedRecipientsForEditJob() {
    if (!editingBroadcastJobId) {
        showNotification('Сначала открой редактирование задачи', 'error');
        return;
    }
    const ok = confirm(
        'Вернуть уже обработанных получателей (sent/failed/skipped) обратно в очередь?\n\n'
        + 'Это позволит повторно отправить по этой же базе с новым текстом.'
    );
    if (!ok) return;
    const btn = document.getElementById('bcRequeueProcessedBtn');
    const prev = btn?.textContent || '';
    if (btn) {
        btn.disabled = true;
        btn.textContent = '↺ Возвращаю...';
    }
    try {
        const res = await fetch(`${API_URL}/broadcast/jobs/${editingBroadcastJobId}/requeue-processed`, {
            method: 'POST',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        const data = await res.json().catch(() => ({}));
        if (!res.ok) throw new Error(data.detail || 'Ошибка возврата получателей');
        showNotification(`✅ ${data.message || 'Получатели возвращены в очередь'}`, 'success');
        // Обновим подпись состояния в textarea, чтобы сразу было видно pending.
        const detailsRes = await fetch(`${API_URL}/broadcast/jobs/${editingBroadcastJobId}`, {
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (detailsRes.ok) {
            const payload = await detailsRes.json();
            const qs = payload?.queue_stats || {};
            const info = `Текущее состояние: ${qs.pending || 0} в очереди, ${qs.sent || 0} отправлено, ${qs.failed || 0} ошибок, ${qs.skipped || 0} пропущено`;
            if (broadcastSource === 'manual') {
                const ta = document.getElementById('bcManualUsers');
                if (ta && ta.value.includes('получатели уже загружены')) {
                    ta.value = `(получатели уже загружены — ${info})\n(оставьте поле как есть или введите новых ниже)`;
                }
            } else if (broadcastSource === 'phones') {
                const ta = document.getElementById('bcPhonesManual');
                if (ta && ta.value.includes('номера уже загружены')) {
                    ta.value = `(номера уже загружены — ${info})\n(оставьте как есть или введите новых)`;
                }
            }
        }
    } catch (e) {
        showNotification(e.message || 'Ошибка возврата получателей', 'error');
    } finally {
        if (btn) {
            btn.disabled = false;
            btn.textContent = prev || '↺ Сбросить получателей';
        }
    }
}

async function clearRecipientsForEditJob() {
    if (!editingBroadcastJobId) {
        showNotification('Сначала открой редактирование задачи', 'error');
        return;
    }
    const ok = confirm(
        'Полностью очистить получателей этой задачи?\n\n'
        + 'После этого можно добавить только нужные теги/номера и запустить чистый тест.'
    );
    if (!ok) return;
    const btn = document.getElementById('bcClearRecipientsBtn');
    const prev = btn?.textContent || '';
    if (btn) {
        btn.disabled = true;
        btn.textContent = '🧹 Очищаю...';
    }
    try {
        const res = await fetch(`${API_URL}/broadcast/jobs/${editingBroadcastJobId}/clear-recipients`, {
            method: 'POST',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        const data = await res.json().catch(() => ({}));
        if (!res.ok) throw new Error(data.detail || 'Ошибка очистки получателей');
        showNotification(`✅ ${data.message || 'Список получателей очищен'}`, 'success');
        const ta = document.getElementById('bcManualUsers');
        if (ta && (broadcastSource === 'manual' || broadcastSource === 'parsed')) {
            ta.value = '';
        }
        const taPhones = document.getElementById('bcPhonesManual');
        if (taPhones && broadcastSource === 'phones') {
            taPhones.value = '';
            updateBcPhonesManualCount();
        }
    } catch (e) {
        showNotification(e.message || 'Ошибка очистки получателей', 'error');
    } finally {
        if (btn) {
            btn.disabled = false;
            btn.textContent = prev || '🧹 Очистить получателей';
        }
    }
}

// ============== AI FREEMODEL ==============

function _stripSpintaxToPlain(text) {
    let out = String(text || '');
    for (let i = 0; i < 24 && out.includes('{'); i++) {
        out = out.replace(/\{([^{}]+)\}/g, (_, inner) => {
            const parts = inner.split('|').map(s => s.trim()).filter(Boolean);
            return parts[0] || '';
        });
    }
    return out.replace(/\s+/g, ' ').trim();
}

function _capturePlainSource() {
    const ta = document.getElementById('bcMessageText');
    const v = (ta?.value || '').trim();
    if (!v) return '';
    if (!v.includes('{')) {
        _originalText = v;
        return v;
    }
    if (_originalText && !_originalText.includes('{')) {
        return _originalText.trim();
    }
    const plain = _stripSpintaxToPlain(v);
    if (plain) _originalText = plain;
    return plain;
}

function _broadcastTextForSave() {
    const raw = document.getElementById('bcMessageText')?.value.trim() || '';
    _syncSelectedVariantSpintax();
    if (_bcAiSpintax) return _bcAiSpintax;
    if (raw.includes('{')) return raw;
    return _capturePlainSource() || raw;
}

function _broadcastShouldRandomize(text) {
    _syncSelectedVariantSpintax();
    return _selectedBcVariants().length > 1 || String(text || '').includes('{');
}

function _renderAiPreview(variants, meta) {
    const el = document.getElementById('spintaxPreviewResult');
    if (!el) return;
    _syncSelectedVariantSpintax();
    const selected = _selectedBcVariants();
    const selectedKeys = new Set(selected.map(v => _bcVariantKey(v)));
    const candidates = (variants || []).filter(v => !selectedKeys.has(_bcVariantKey(v)));
    const status = window._bcAiStatusHtml || '';
    const head = 
        (status ? `<div style="margin-bottom:6px;font-size:0.72rem;">${status}</div>` : '') +
        (meta ? `<div style="margin-bottom:6px;color:var(--success);font-size:0.72rem;">${escapeHtml(meta)} · закреплено: ${selected.length} · новых: ${candidates.length}</div>` : '');
    const renderRow = (v, i, selectedRow) => {
        const idx = _bcAiVariants.findIndex(x => _bcVariantKey(x) === _bcVariantKey(v));
        const key = _bcVariantKey(v);
        const border = selectedRow ? 'rgba(72,187,120,0.85)' : 'var(--border)';
        const bg = selectedRow ? 'rgba(72,187,120,0.10)' : 'transparent';
        const mark = selectedRow ? '✓' : '+';
        return `<div class="bc-ai-variant" data-idx="${i}" style="display:flex;gap:7px;align-items:flex-start;padding:6px 8px;margin-bottom:4px;border-radius:6px;border:1px solid ${border};background:${bg};font-size:0.78rem;line-height:1.4;">` +
        `<button type="button" onclick="applyBcAiVariant(${idx})" title="Выбрать/снять" style="width:22px;height:22px;line-height:18px;border-radius:50%;border:1px solid ${border};background:transparent;color:${selectedRow ? 'var(--success)' : 'var(--text-secondary)'};cursor:pointer;flex-shrink:0;">${mark}</button>` +
        `<div onclick="applyBcAiVariant(${idx})" style="cursor:pointer;flex:1;"><span style="color:var(--text-secondary);margin-right:6px;">${i + 1}.</span>${escapeHtml(v)}</div>` +
        `<button type="button" onclick="deleteBcAiVariant(${idx})" title="Удалить вариант" style="border:0;background:transparent;color:var(--text-secondary);cursor:pointer;font-size:0.85rem;flex-shrink:0;">✕</button></div>`;
    };
    const selectedBlock = selected.length
        ? `<div style="margin:6px 0 5px;color:var(--success);font-size:0.72rem;font-weight:600;">Закреплены для отправки</div>` +
          selected.map((v, i) => renderRow(v, i, true)).join('')
        : '';
    const candidateBlock = candidates.length
        ? `<div style="margin:8px 0 5px;color:var(--text-secondary);font-size:0.72rem;font-weight:600;">Новые варианты (выбери нужные)</div>` +
          candidates.map((v, i) => renderRow(v, i, false)).join('')
        : '<div style="color:var(--text-secondary);font-size:0.72rem;margin-top:5px;">Новых вариантов нет. Нажми генерацию ещё раз.</div>';
    el.innerHTML = head + selectedBlock + candidateBlock +
    `<div style="font-size:0.68rem;color:var(--text-secondary);margin-top:6px;line-height:1.45;">✓ отмеченные варианты закреплены и пойдут в рассылку. Новая генерация заменит только невыбранные. <button type="button" onclick="addCurrentTextAsVariant()" style="margin-left:6px;padding:2px 7px;font-size:0.68rem;border:1px solid var(--border);background:var(--bg-hover);color:var(--text-secondary);border-radius:4px;cursor:pointer;">+ добавить текст как вариант</button></div>`;
}

function applyBcAiVariant(idx) {
    if (!_bcAiVariants[idx]) return;
    window._bcAiStatusHtml = '';
    const key = _bcVariantKey(_bcAiVariants[idx]);
    if (_bcSelectedVariantKeys.has(key)) _bcSelectedVariantKeys.delete(key);
    else _bcSelectedVariantKeys.add(key);
    _syncSelectedVariantSpintax();
    _renderAiPreview(_bcAiVariants, 'Варианты для отправки');
}

function _bcVariantKey(text) {
    return _bcNormText(text).slice(0, 500);
}

function _selectedBcVariants() {
    return _bcAiVariants.filter(v => _bcSelectedVariantKeys.has(_bcVariantKey(v)));
}

function _syncSelectedVariantSpintax() {
    const selected = _selectedBcVariants();
    _bcAiSpintax = selected.length ? _bcVariantsToSpintax(selected) : '';
    return selected;
}

function _replaceUnselectedBcVariants(newVariants) {
    const selected = _selectedBcVariants();
    const merged = [...selected];
    for (const v of _bcDedupeVariants(newVariants || [])) {
        const text = String(v || '').trim();
        if (!text) continue;
        if (merged.some(x => _bcTooSimilar(text, x))) continue;
        merged.push(text);
    }
    _bcAiVariants = merged;
    _bcSelectedVariantKeys = new Set(selected.map(v => _bcVariantKey(v)));
    _syncSelectedVariantSpintax();
}

function _appendSelectedBcVariant(text) {
    const clean = String(text || '').trim();
    if (!clean) return;
    if (!_bcAiVariants.some(x => _bcTooSimilar(clean, x))) {
        _bcAiVariants.push(clean);
    }
    _bcSelectedVariantKeys.add(_bcVariantKey(clean));
    _syncSelectedVariantSpintax();
}

function deleteBcAiVariant(idx) {
    if (!_bcAiVariants[idx]) return;
    window._bcAiStatusHtml = '';
    _bcSelectedVariantKeys.delete(_bcVariantKey(_bcAiVariants[idx]));
    _bcAiVariants.splice(idx, 1);
    _syncSelectedVariantSpintax();
    _renderAiPreview(_bcAiVariants, 'Варианты для отправки');
}

function addCurrentTextAsVariant() {
    window._bcAiStatusHtml = '';
    const text = (document.getElementById('bcMessageText')?.value || '').trim();
    if (!text) {
        showNotification('В поле текста пусто', 'error');
        return;
    }
    _appendSelectedBcVariant(text);
    _renderAiPreview(_bcAiVariants, 'Варианты для отправки');
}

const BC_AI_OPENAI_MODELS = ['gpt-5.4', 'gpt-5.4-mini', 'gpt-5.5', 'gpt-5.3-codex'];
const BC_AI_DEFAULT_MODEL = 'gpt-5.4';
const BC_AI_PROMPT_LS = 'spamimpakter_bc_ai_prompt';
const BC_AI_VARIANT_COUNT_LS = 'spamimpakter_bc_ai_variant_count';
const BC_AI_VARIANT_COUNT_MAX = 25;
const BC_AI_PROFILES_LS = 'spamimpakter_bc_ai_profiles_v1';
const BC_AI_BUILTIN_PROMPTS = {
    business: `Ты сильный менеджер по продажам и коммуникациям клиентского сервиса.

Твоя задача: по черновику пользователя написать сильные деловые варианты сообщения.

Стиль:
- грамотный русский язык;
- спокойно, уверенно, уважительно;
- без ощущения массовой рассылки;
- без давления, крика, рекламных клише и искусственного "нейросетевого" тона;
- как живой менеджер проекта, который пишет конкретному человеку.

Подход:
- сохраняй смысл черновика, но не копируй фразы дословно;
- делай варианты реально разными по структуре и формулировкам;
- не добавляй факты, которых нет в черновике;
- если термин из черновика важный, не заменяй его на неподходящий синоним;
- лучше меньше сильных вариантов, чем много слабых.

Логика продаж:
- сначала короткий контекст или уважительное обращение;
- затем понятный вопрос/предложение;
- при необходимости мягко обозначь пользу или следующий шаг;
- сообщение должно открывать диалог, а не давить на продажу.`,
};

function _bcAiCustomProfiles() {
    try {
        const raw = JSON.parse(localStorage.getItem(BC_AI_PROFILES_LS) || '{}');
        return raw && typeof raw === 'object' ? raw : {};
    } catch (_) {
        return {};
    }
}

function _saveBcAiCustomProfiles(profiles) {
    try {
        localStorage.setItem(BC_AI_PROFILES_LS, JSON.stringify(profiles || {}));
    } catch (_) {}
}

function _bcAiProfileKey() {
    return document.getElementById('bcAiStyle')?.value || 'business';
}

function _bcAiPromptStorageKey(profile) {
    return `${BC_AI_PROMPT_LS}_${profile || 'business'}`;
}

function refreshBcAiProfiles() {
    const sel = document.getElementById('bcAiStyle');
    if (!sel) return;
    const current = sel.value || 'business';
    const builtins = [
        ['business', 'Бизнес'],
    ];
    const custom = _bcAiCustomProfiles();
    sel.innerHTML = builtins.map(([v, t]) => `<option value="${v}">${t}</option>`).join('');
    Object.keys(custom).sort().forEach(name => {
        sel.insertAdjacentHTML('beforeend', `<option value="custom:${escapeHtml(name)}">⭐ ${escapeHtml(name)}</option>`);
    });
    if ([...sel.options].some(o => o.value === current)) sel.value = current;
}

function loadBcAiPromptFromStorage() {
    const el = document.getElementById('bcAiPrompt');
    if (!el) return;
    const profile = _bcAiProfileKey();
    const custom = _bcAiCustomProfiles();
    try {
        if (profile.startsWith('custom:')) {
            const name = profile.slice(7);
            el.value = custom[name]?.prompt || '';
            const nameInput = document.getElementById('bcAiProfileName');
            if (nameInput) nameInput.value = name;
            return;
        }
        const nameInput = document.getElementById('bcAiProfileName');
        if (nameInput) nameInput.value = '';
        let saved = localStorage.getItem(_bcAiPromptStorageKey(profile));
        if (profile === 'business' && saved && saved.length < 180) {
            saved = '';
            localStorage.removeItem(_bcAiPromptStorageKey(profile));
        }
        el.value = saved || BC_AI_BUILTIN_PROMPTS[profile] || '';
    } catch (_) {}
}

function saveBcAiPromptToStorage() {
    const el = document.getElementById('bcAiPrompt');
    if (!el) return;
    try {
        const profile = _bcAiProfileKey();
        if (!profile.startsWith('custom:')) {
            localStorage.setItem(_bcAiPromptStorageKey(profile), el.value.trim());
        }
    } catch (_) {}
}

function onBcAiProfileChange() {
    loadBcAiPromptFromStorage();
    loadBcAiVariantCount();
}

function saveBcAiCustomProfile() {
    const nameInput = document.getElementById('bcAiProfileName');
    const promptEl = document.getElementById('bcAiPrompt');
    const name = (nameInput?.value || '').trim();
    const prompt = (promptEl?.value || '').trim();
    if (!name) {
        showNotification('Напиши имя профиля', 'error');
        return;
    }
    if (!prompt) {
        showNotification('Напиши инструкцию профиля', 'error');
        return;
    }
    const profiles = _bcAiCustomProfiles();
    profiles[name] = { prompt, created_at: new Date().toISOString() };
    _saveBcAiCustomProfiles(profiles);
    refreshBcAiProfiles();
    const sel = document.getElementById('bcAiStyle');
    if (sel) sel.value = `custom:${name}`;
    showNotification(`Профиль «${name}» сохранён`, 'success');
}

function deleteBcAiCustomProfile() {
    const sel = document.getElementById('bcAiStyle');
    const profile = sel?.value || '';
    if (!profile.startsWith('custom:')) {
        showNotification('Можно удалить только свой профиль', 'error');
        return;
    }
    const name = profile.slice(7);
    const profiles = _bcAiCustomProfiles();
    delete profiles[name];
    _saveBcAiCustomProfiles(profiles);
    if (sel) sel.value = 'business';
    refreshBcAiProfiles();
    loadBcAiPromptFromStorage();
    loadBcAiVariantCount();
    showNotification(`Профиль «${name}» удалён`, 'success');
}

function _bcAutoreplyTemplateId() {
    const sel = document.getElementById('bcAutoreplyTpl');
    if (!sel || !sel.value) return null;
    const n = parseInt(sel.value, 10);
    return Number.isFinite(n) && n > 0 ? n : null;
}

function _syncBcAutoreplyTplState() {
    const on = document.getElementById('bcAutoreplyEnabled')?.checked;
    const sel = document.getElementById('bcAutoreplyTpl');
    const hint = document.getElementById('bcAutoreplyHint');
    if (sel) sel.disabled = !on;
    if (hint) {
        const loadedCount = Number(sel?.dataset.templateCount || 0);
        hint.textContent = on
            ? (loadedCount > 0
                ? `Шаблонов: ${loadedCount}. Только получатели этой задачи — не путать с глобальным авто.`
                : 'Создай: Автоответы → ИИ для задач, затем выбери здесь.')
            : 'Включи галочку — автоответ только получателям этой рассылки.';
    }
}

async function loadBcAutoreplyTemplates(selectedId) {
    const sel = document.getElementById('bcAutoreplyTpl');
    if (!sel) return;
    const hint = document.getElementById('bcAutoreplyHint');
    try {
        sel.innerHTML = '<option value="">— загрузка шаблонов… —</option>';
        const res = await fetch(`${API_URL}/templates`, {
            cache: 'no-store',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (!res.ok) {
            throw new Error(`HTTP ${res.status}`);
        }
        const list = await res.json();
        const all = Array.isArray(list) ? list : (list.templates || list.value || []);
        const jobRows = all.filter(t => (t.ar_scope || '').toLowerCase() === 'job');
        const rows = jobRows.length ? jobRows : all;
        sel.dataset.templateCount = String(rows.length);
        sel.innerHTML = '<option value="">— выберите шаблон —</option>' +
            rows.map(t => {
                const tag = (t.ar_scope || '').toLowerCase() === 'job' ? '📨 ' : '';
                return `<option value="${t.id}">${tag}${(t.title || 'Без названия').replace(/</g, '&lt;')}</option>`;
            }).join('');
        if (selectedId) sel.value = String(selectedId);
        if (hint && !jobRows.length) {
            hint.textContent = all.length
                ? 'Нет пресетов. Создай: Автоответы → ИИ для задач. Показаны все.'
                : 'Пресетов нет. Создай: Автоответы → ИИ для задач.';
        }
    } catch (e) {
        sel.dataset.templateCount = '0';
        sel.innerHTML = '<option value="">— не удалось загрузить шаблоны —</option>';
        if (hint) hint.textContent = `Не удалось загрузить шаблоны: ${e.message || e}`;
    }
    _syncBcAutoreplyTplState();
}

document.addEventListener('change', e => {
    if (e.target?.id === 'bcAutoreplyEnabled') {
        _syncBcAutoreplyTplState();
        if (e.target.checked) loadBcAutoreplyTemplates(_bcAutoreplyTemplateId());
    }
});
document.addEventListener('focusin', e => {
    if (e.target?.id === 'bcAutoreplyTpl' && e.target.options.length <= 1) {
        loadBcAutoreplyTemplates(_bcAutoreplyTemplateId());
    }
});

function _bcAiVariantCount() {
    const el = document.getElementById('bcAiVariantCount');
    const n = parseInt(el?.value, 10);
    if (!Number.isFinite(n)) return 10;
    return Math.max(1, Math.min(n, BC_AI_VARIANT_COUNT_MAX));
}

function loadBcAiVariantCount() {
    const el = document.getElementById('bcAiVariantCount');
    if (!el) return;
    try {
        const saved = parseInt(localStorage.getItem(BC_AI_VARIANT_COUNT_LS), 10);
        if (Number.isFinite(saved)) el.value = String(Math.max(1, Math.min(saved, BC_AI_VARIANT_COUNT_MAX)));
    } catch (_) {}
}

function saveBcAiVariantCount() {
    const el = document.getElementById('bcAiVariantCount');
    if (!el) return;
    try {
        localStorage.setItem(BC_AI_VARIANT_COUNT_LS, String(_bcAiVariantCount()));
        el.value = String(_bcAiVariantCount());
    } catch (_) {}
}

function _bcNormText(s) {
    return String(s || '')
        .toLowerCase()
        .replace(/ё/g, 'е')
        .replace(/[^\p{L}\p{N}\s]+/gu, ' ')
        .replace(/\s+/g, ' ')
        .trim();
}

function _bcTooSimilar(a, b) {
    const na = _bcNormText(a);
    const nb = _bcNormText(b);
    if (!na || !nb) return false;
    if (na === nb) return true;
    if (na.length > 20 && (na.includes(nb) || nb.includes(na))) return true;
    const wa = new Set(na.split(' ').filter(w => w.length >= 4));
    const wb = new Set(nb.split(' ').filter(w => w.length >= 4));
    const small = Math.min(wa.size, wb.size);
    if (small < 5) return false;
    let common = 0;
    wa.forEach(w => { if (wb.has(w)) common += 1; });
    return (common / small) >= 0.72;
}

function _bcDedupeVariants(variants) {
    const out = [];
    for (const v of (variants || [])) {
        const text = String(v || '').trim();
        if (!text) continue;
        if (out.some(x => _bcTooSimilar(text, x))) continue;
        out.push(text);
    }
    return out;
}

function _bcVariantsToSpintax(variants) {
    const safe = (variants || [])
        .map(v => String(v || '').replace(/[{}]/g, '').replace(/\|/g, '/').trim())
        .filter(Boolean);
    if (safe.length <= 1) return safe[0] || '';
    return `{${safe.join('|')}}`;
}

function _bcSplitTopLevelVariants(inner) {
    const out = [];
    let cur = '';
    let depth = 0;
    for (let i = 0; i < inner.length; i++) {
        const ch = inner[i];
        if (ch === '{') {
            depth += 1;
            cur += ch;
            continue;
        }
        if (ch === '}') {
            depth = Math.max(0, depth - 1);
            cur += ch;
            continue;
        }
        if (ch === '|' && depth === 0) {
            out.push(cur.trim());
            cur = '';
            continue;
        }
        cur += ch;
    }
    if (cur.trim()) out.push(cur.trim());
    return out.filter(Boolean);
}

function _bcParseTopLevelSpintaxVariants(text) {
    const t = String(text || '').trim();
    if (!t.startsWith('{') || !t.endsWith('}')) return [];
    const inner = t.slice(1, -1);
    if (!inner.includes('|')) return [];
    return _bcSplitTopLevelVariants(inner);
}

function _hydrateBcVariantsFromSavedText(savedText) {
    const raw = String(savedText || '').trim();
    const variants = _bcParseTopLevelSpintaxVariants(raw);
    if (variants.length < 2) return false;
    _bcAiVariants = _bcDedupeVariants(variants);
    _bcSelectedVariantKeys = new Set(_bcAiVariants.map(v => _bcVariantKey(v)));
    _syncSelectedVariantSpintax();
    const ta = document.getElementById('bcMessageText');
    if (ta) ta.value = _bcAiVariants[0] || _stripSpintaxToPlain(raw);
    _originalText = _bcAiVariants[0] || _stripSpintaxToPlain(raw);
    _renderAiPreview(_bcAiVariants, 'Варианты восстановлены из сохранённой задачи');
    return true;
}

async function syncBcAiFromSettings() {
    const prov = document.getElementById('bcAiProvider');
    if (!prov) return;
    let s = null;
    try {
        const sres = await fetch(`${API_URL}/settings`);
        if (sres.ok) s = await sres.json();
    } catch (_) {}
    if (s) {
        prov.value = 'openai';
        const style = document.getElementById('bcAiStyle');
        if (style && s.freemodel_style && [...style.options].some(o => o.value === s.freemodel_style)) {
            style.value = s.freemodel_style;
        }
    }
    await loadBcAiModels(s);
}

async function loadBcAiModels(settings) {
    const sel = document.getElementById('bcAiModel');
    const provEl = document.getElementById('bcAiProvider');
    if (!sel || !provEl) return;
    const provider = provEl.value || 'openai';
    if (provider === 'anthropic') {
        const models = ['claude-t1', 'claude-t0'];
        const pref = settings?.freemodel_model_anthropic || 'claude-t1';
        sel.innerHTML = models.map(m => `<option value="${m}">${m}</option>`).join('');
        sel.value = models.includes(pref) ? pref : 'claude-t1';
        return;
    }
    let models = [...BC_AI_OPENAI_MODELS];
    try {
        const res = await fetch(`${API_URL}/ai/models?provider=openai`);
        if (res.ok) {
            const data = await res.json();
            const remote = data.models || [];
            if (remote.length) {
                const ordered = [BC_AI_DEFAULT_MODEL, ...BC_AI_OPENAI_MODELS.filter(m => m !== BC_AI_DEFAULT_MODEL)];
                models = ordered.filter(m => remote.includes(m));
                remote.forEach(m => { if (!models.includes(m)) models.push(m); });
            }
        }
    } catch (_) {}
    const pref = settings?.freemodel_model_openai || BC_AI_DEFAULT_MODEL;
    const pick = models.includes(pref) ? pref : (models.includes(BC_AI_DEFAULT_MODEL) ? BC_AI_DEFAULT_MODEL : models[0]);
    sel.innerHTML = models.map(m => `<option value="${m}">${m}</option>`).join('');
    sel.value = pick || BC_AI_DEFAULT_MODEL;
}

function _aiShowStatus(html) {
    const el = document.getElementById('spintaxPreviewResult');
    window._bcAiStatusHtml = html;
    if (_bcAiVariants.length) {
        _renderAiPreview(_bcAiVariants, '');
        return;
    }
    if (el) {
        el.innerHTML = `<div style="margin-bottom:6px;">${html}</div>`;
        el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }
}

function _aiHumanError(res, rawMsg) {
    if (res?.status === 400 && /ключ|key/i.test(String(rawMsg || ''))) {
        return 'Не настроен API-ключ для генерации.';
    }
    if (res?.status === 404) {
        return 'Генератор временно недоступен. Перезапустите приложение и попробуйте снова.';
    }
    if (res?.status === 502 && rawMsg) {
        return String(rawMsg);
    }
    return 'Не удалось сгенерировать варианты. Попробуйте повторить.';
}

function _aiRetryButton() {
    return '<br><button type="button" onclick="aiGenerateSpintax()" style="margin-top:6px;padding:4px 12px;font-size:0.72rem;border:1px solid #7c4dff;background:rgba(124,77,255,0.2);color:#fff;border-radius:4px;cursor:pointer;">↻ Повторить генерацию</button>';
}

async function aiGenerateSpintax() {
    const plain = _capturePlainSource();
    const preview = document.getElementById('spintaxPreviewResult');
    if (!plain) {
        _aiShowStatus('<span style="color:#fc8181;font-weight:600;">⚠ Введите текст сообщения выше, потом жмите AI</span>');
        return;
    }

    const btn = document.getElementById('bcAiGenBtn');
    const prev = btn ? btn.textContent : '';
    if (btn) { btn.disabled = true; btn.textContent = '⏳ Генерация…'; }

    const provider = document.getElementById('bcAiProvider')?.value || 'openai';
    let model = document.getElementById('bcAiModel')?.value;
    if (!model) {
        await loadBcAiModels();
        model = document.getElementById('bcAiModel')?.value;
    }
    if (!model) model = BC_AI_DEFAULT_MODEL;
    const style = document.getElementById('bcAiStyle')?.value || 'business';
    const pinned = _selectedBcVariants();
    let userPrompt = (document.getElementById('bcAiPrompt')?.value || '').trim();
    const variantCount = _bcAiVariantCount();
    saveBcAiVariantCount();
    if (pinned.length) {
        userPrompt += `\n\nУже закреплённые варианты, НЕ повторяй их и не делай похожие:\n` +
            pinned.map((v, i) => `${i + 1}. ${v}`).join('\n');
    }
    saveBcAiPromptToStorage();

    _aiShowStatus(`<span style="color:#b794f6;">⏳ Запрос к ${model}: ${variantCount} вариантов… до 90 сек (при 504 сам повторим / mini)</span>`);

    let res, data;
    try {
        res = await fetch(`/api/spintax/ai-generate`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                text: plain,
                provider,
                model,
                style,
                user_prompt: userPrompt || null,
                count: variantCount
            })
        });
        data = await res.json().catch(() => ({}));
    } catch (e) {
        console.error('[AI]', e);
        _aiShowStatus(`<span style="color:#fc8181;font-weight:600;">❌ Не удалось сгенерировать варианты. Попробуйте повторить.</span>${_aiRetryButton()}`);
        if (btn) { btn.disabled = false; btn.textContent = prev || '🧠 AI: варианты'; }
        return;
    }

    if (!res.ok) {
        let msg = data.detail || data.message || `HTTP ${res.status}`;
        if (typeof msg !== 'string') msg = JSON.stringify(msg);
        console.error('[AI API]', res.status, msg, data);
        _aiShowStatus(`<span style="color:#fc8181;font-weight:600;">❌ ${escapeHtml(_aiHumanError(res, msg))}</span>${_aiRetryButton()}`);
        if (btn) { btn.disabled = false; btn.textContent = prev || '🧠 AI: варианты'; }
        return;
    }

    const rawVariants = Array.isArray(data.variants) ? data.variants : [];
    const selectedBefore = _selectedBcVariants().length;
    _replaceUnselectedBcVariants(rawVariants);
    const variants = _bcAiVariants;
    const usage = data.usage || {};
    const uline = usage.total_tokens ? ` · ${usage.total_tokens} токенов` : '';
    const fb = data.fallback_used ? ' · fallback mini' : '';
    const reduced = data.reduced_count_fallback ? ` · снижено до ${rawVariants.length}` : '';
    const newPool = Math.max(0, variants.length - selectedBefore);
    const cut = rawVariants.length > newPool ? ` · дублей пропущено: ${rawVariants.length - newPool}` : '';
    const meta = `AI ${data.provider || provider} / ${data.model || model}${uline}${fb}${reduced} · закреплено: ${selectedBefore} · новых: ${newPool}${cut}`;
    window._bcAiStatusHtml = '';
    _renderAiPreview(variants, meta);
    if (btn) { btn.disabled = false; btn.textContent = prev || '🧠 AI: варианты'; }
}

// ============== SPINTAX AUTO-GENERATOR ==============

// Большой словарь синонимов для авто-замены (100+ слов)
const _synonyms = {
    // Приветствия
    'привет': ['здравствуй','хей','приветствую','салют','здарова','йо'],
    'здравствуй': ['привет','хей','приветствую','салют','здарова'],
    'здравствуйте': ['привет','добрый день','приветствую','здарова'],
    'хей': ['привет','здравствуй','йо','салют','здарова'],
    'здорова': ['привет','хей','салют','здарова','йо','приветствую'],
    'здарова': ['привет','хей','салют','здорова','йо'],
    'добрый день': ['привет','здравствуйте','приветствую','доброго дня'],
    'пока': ['до свидания','до встречи','увидимся','бывай'],
    // Вопросы
    'как дела': ['как жизнь','как ты','как поживаешь','как оно','что нового'],
    'как жизнь': ['как дела','как ты','как оно','что нового'],
    // Действия
    'заходи': ['переходи','зайди','кликай','жми','нажми','залетай'],
    'переходи': ['заходи','зайди','кликай','жми','залетай'],
    'залетай': ['заходи','переходи','зайди','жми','кликай'],
    'скачать': ['загрузить','установить','забрать','получить'],
    'загрузить': ['скачать','установить','забрать','получить','скачивай'],
    'смотри': ['гляди','глянь','посмотри','чекай','зацени'],
    'посмотри': ['гляди','глянь','смотри','чекай','зацени'],
    'чекай': ['смотри','глянь','зацени','гляди'],
    'зацени': ['смотри','глянь','чекай','оцени','посмотри'],
    'написал': ['подготовил','сделал','создал','запилил'],
    'сделал': ['подготовил','создал','написал','запилил','организовал'],
    'запустили': ['стартанули','выпустили','открыли','начали','организовали'],
    'выходит': ['выпускается','появляется','стартует','запускается','релизится'],
    'получить': ['забрать','заполучить','взять','урвать'],
    'купить': ['приобрести','взять','заказать','оформить'],
    'показать': ['продемонстрировать','поделиться','рассказать про'],
    'рассказать': ['поведать','поделиться','показать','сообщить'],
    'ждём': ['ожидаем','будем рады видеть','ждём вас'],
    'участвуй': ['принимай участие','участвуй','заходи','присоединяйся'],
    'присоединяйся': ['вступай','заходи','залетай','участвуй'],
    // Прилагательные  
    'новый': ['свежий','обновлённый','актуальный','последний'],
    'новое': ['свежее','крутое','классное','интересное','отличное'],
    'свежий': ['новый','обновлённый','актуальный','горячий','последний'],
    'свежее': ['новое','крутое','классное','горячее'],
    'крутой': ['классный','огненный','топовый','мощный','отличный'],
    'крутое': ['классное','отличное','огненное','топовое'],
    'классный': ['крутой','отличный','огненный','топовый'],
    'классное': ['крутое','отличное','огненное','топовое'],
    'хороший': ['отличный','крутой','классный','замечательный','годный'],
    'большой': ['крупный','масштабный','огромный','значительный','серьёзный'],
    'лучший': ['топовый','отличнейший','крутейший','первоклассный'],
    'интересный': ['любопытный','занимательный','увлекательный','крутой'],
    'бесплатно': ['без оплаты','абсолютно бесплатно','за 0₽','даром','фри'],
    'быстро': ['моментально','мгновенно','за секунду','оперативно','шустро'],
    // Существительные
    'обновление': ['апдейт','обнова','релиз','новинка'],
    'предложение': ['оффер','акция','предложение','штука'],
    'розыгрыш': ['гив','раздача','конкурс','ивент','акция'],
    'раздача': ['розыгрыш','гив','конкурс','акция'],
    'конкурс': ['розыгрыш','гив','раздача','ивент'],
    'приз': ['награда','подарок','выигрыш','лут'],
    'ссылке': ['линку','ссылочке','кнопке'],
    'друг': ['бро','товарищ','приятель','дружище'],
    'ребята': ['пацаны','народ','братья','люди','друзья'],
    'подписчиков': ['фолловеров','участников','юзеров','людей','подписоты'],
    'канала': ['группы','чата','канала','паблика','сообщества'],
    'группы': ['канала','чата','паблика','сообщества','комьюнити'],
    'баксов': ['долларов','$','бачей','зелёных','вечнозелёных'],
    'рублей': ['₽','руб','деревянных','рубчиков'],
    'деньги': ['бабки','средства','финансы','кэш'],
    'проблема': ['вопрос','загвоздка','сложность','задача'],
    'помощь': ['поддержка','содействие','хелп'],
    // Наречия / вводные
    'сегодня': ['на днях','уже сегодня','в этот раз'],
    'сейчас': ['прямо сейчас','в данный момент','уже','на данный момент'],
    'скоро': ['совсем скоро','в ближайшее время','уже скоро','на днях'],
    'очень': ['крайне','максимально','ооочень','реально'],
    'круто': ['классно','здорово','огонь','супер','отлично','топ'],
    'отлично': ['здорово','круто','классно','прекрасно','супер'],
    'нужно': ['необходимо','стоит','надо','следует'],
    'можно': ['есть возможность','реально','доступно'],
    'хочу': ['хотел бы','планирую','собираюсь','решил'],
    'давай': ['го','айда','погнали','ну что','давай-давай'],
    'кстати': ['между прочим','к слову','кста'],
    // Числительные контекст
    'числа': ['числа','го числа','го'],
};

// Вариации пунктуации/эмодзи в конце — безопасны и не ломают смысл.
const _tailVariants = ['', ' 🙂', ' 👍', ' 🔥', ' ✨'];

function _autoSpintaxLocal() {
    const textarea = document.getElementById('bcMessageText');
    let text = textarea.value.trim();
    if (!text) return;

    // Сохраняем оригинал
    if (!_originalText || !text.includes('{')) {
        _originalText = text;
    }

    text = _originalText;
    let result = text;
    let replacements = 0;

    // 1. Замена слов из словаря
    const wordsFound = new Set();
    // Сортируем по длине desc чтобы длинные фразы заменялись первыми
    const sortedEntries = Object.entries(_synonyms).sort((a,b) => b[0].length - a[0].length);
    for (const [word, syns] of sortedEntries) {
        const regex = new RegExp("(^|[\\s,.!?()«»\"'—–\\-])(" + _escapeRegex(word) + ")([\\s,.!?()«»\"'—–\\-]|$)", "gi");
        if (result.match(regex) && !wordsFound.has(word.toLowerCase())) {
            wordsFound.add(word.toLowerCase());
            const shuffled = [...syns].sort(() => Math.random() - 0.5);
            const pick = shuffled.slice(0, 2 + Math.floor(Math.random() * 2));

            let replaced = false;
            result = result.replace(regex, (m, pre, orig, post) => {
                if (replaced) return m; // Только первое вхождение
                replaced = true;
                const variants = [orig, ...pick.map(s => {
                    if (orig[0] === orig[0].toUpperCase()) {
                        return s.charAt(0).toUpperCase() + s.slice(1);
                    }
                    return s;
                })];
                const unique = [...new Set(variants)];
                if (unique.length < 2) return m;
                replacements++;
                return pre + '{' + unique.join('|') + '}' + post;
            });
        }
    }

    // 2. Fallback: если мало замен — вариации пунктуации
    if (replacements < 2) {
        // Восклицательные знаки
        let excReplaced = false;
        result = result.replace(/!+/g, (m) => {
            if (!excReplaced) {
                excReplaced = true;
                replacements++;
                return '{!|!!|! 🔥|! ✨}';
            }
            return m;
        });
    }

    // 3. Fallback: лёгкая вариация концовки (эмодзи), смысл не меняем
    if (replacements < 3) {
        const tails = [..._tailVariants].sort(() => Math.random() - 0.5).slice(0, 4);
        const uniqTails = [...new Set(tails)];
        if (uniqTails.length >= 2) {
            result = result.replace(/\s*$/, '') + '{' + uniqTails.join('|') + '}';
            replacements++;
        }
    }

    // 4. Финальный fallback: если вообще 0 замен — добавить рандомный emoji/вводную
    if (replacements === 0) {
        const emojis = ['👋','🔥','✨','💡','⚡','👀','🎯','💪','🚀','📢'];
        const shuffEmoji = emojis.sort(() => Math.random() - 0.5).slice(0, 3);
        result = '{' + shuffEmoji.join('|') + '|} ' + result;
        replacements++;
    }

    textarea.value = result;

    const preview = _resolveSpintax(result);
    document.getElementById('spintaxPreviewResult').innerHTML =
        `<span style="color:var(--success);">✅ ${replacements} замен. Превью: </span><span style="color:var(--text-primary);">${escapeHtml(preview)}</span>`;
}

async function autoSpintax() {
    const plain = _capturePlainSource();
    if (!plain) return;

    try {
        const res = await fetch(`${API_URL}/spintax/enrich`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ text: plain, max_blocks: 25 })
        });
        if (!res.ok) throw new Error('enrich failed');
        const data = await res.json();
        const enriched = data.text || plain;
        const pv = await fetch(`${API_URL}/spintax/preview`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ text: enriched, count: 10, auto: true })
        });
        if (!pv.ok) throw new Error('preview failed');
        const pdata = await pv.json();
        const variants = Array.isArray(pdata.variants) ? pdata.variants : [];
        _replaceUnselectedBcVariants(variants);
    } catch (_) {
        const textarea = document.getElementById('bcMessageText');
        const oldText = textarea?.value || plain;
        if (textarea) textarea.value = plain;
        _autoSpintaxLocal();
        const enriched = textarea?.value || plain;
        if (textarea) textarea.value = oldText;
        const local = [];
        for (let i = 0; i < 8; i++) local.push(_resolveSpintax(enriched));
        _replaceUnselectedBcVariants(local);
    }
    _renderAiPreview(_bcAiVariants, 'Spintax варианты (без AI)');
}

async function regenSpintax() {
    if (_originalText) {
        document.getElementById('bcMessageText').value = _originalText;
        await autoSpintax();
    }
}

function clearSpintax() {
    window._bcAiStatusHtml = '';
    _bcAiSpintax = '';
    _bcAiVariants = [];
    _bcSelectedVariantKeys = new Set();
    document.getElementById('spintaxPreviewResult').innerHTML = '<span style="color:var(--text-secondary);">Варианты очищены. Черновик не изменён.</span>';
}

function _escapeRegex(str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function _resolveSpintax(text) {
    let result = text;
    let iter = 0;
    while (result.includes('{') && iter < 20) {
        result = result.replace(/\{([^{}]+)\}/g, (m, p) => {
            const opts = p.split('|');
            return opts[Math.floor(Math.random() * opts.length)];
        });
        iter++;
    }
    return result;
}

async function previewSpintaxResult() {
    if (_bcAiVariants.length) {
        _renderAiPreview(_bcAiVariants, 'AI-варианты (для рассылки)');
        return;
    }
    const text = document.getElementById('bcMessageText').value;
    if (!text) return;
    try {
        const res = await fetch(`${API_URL}/spintax/preview`, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${localStorage.getItem('token')}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ text, count: 10, auto: true })
        });
        if (!res.ok) throw new Error('preview failed');
        const data = await res.json();
        const variants = Array.isArray(data.variants) ? data.variants.slice(0, 10) : [];
        if (!variants.length) throw new Error('empty variants');
        document.getElementById('spintaxPreviewResult').innerHTML =
            `<div style="color:var(--success);font-size:0.72rem;line-height:1.45;">` +
            variants.map((v, i) => `<div>${i + 1}. ${escapeHtml(v)}</div>`).join('') +
            `</div>`;
        return;
    } catch (_) {}

    const r1 = _resolveSpintax(text);
    const r2 = _resolveSpintax(text);
    const r3 = _resolveSpintax(text);
    document.getElementById('spintaxPreviewResult').innerHTML =
        `<div style="color:var(--success);font-size:0.72rem;line-height:1.6;">` +
        `<div>→ ${escapeHtml(r1)}</div>` +
        `<div>→ ${escapeHtml(r2)}</div>` +
        `<div>→ ${escapeHtml(r3)}</div></div>`;
}

function setBroadcastContentType(type) {
    broadcastContentType = type;
    document.getElementById('bcTypeText').style.borderColor = type === 'text' ? 'var(--primary)' : 'var(--border)';
    document.getElementById('bcTypeText').style.background = type === 'text' ? 'var(--primary)' : 'var(--bg-hover)';
    document.getElementById('bcTypeText').style.color = type === 'text' ? '#000' : 'var(--text-primary)';
    document.getElementById('bcTypePost').style.borderColor = type === 'post' ? 'var(--primary)' : 'var(--border)';
    document.getElementById('bcTypePost').style.background = type === 'post' ? 'var(--primary)' : 'var(--bg-hover)';
    document.getElementById('bcTypePost').style.color = type === 'post' ? '#000' : 'var(--text-primary)';
    document.getElementById('bcTextBlock').style.display = type === 'text' ? 'block' : 'none';
    document.getElementById('bcPostBlock').style.display = type === 'post' ? 'block' : 'none';
    if (type === 'post' && document.querySelectorAll('.bc-post-link-row').length === 0) {
        addPostLinkRow();
    }
}

function addPostLinkRow(url, limit) {
    const container = document.getElementById('bcPostLinksContainer');
    const row = document.createElement('div');
    row.className = 'bc-post-link-row';
    row.style.cssText = 'display:flex;gap:6px;align-items:center;margin-bottom:6px;';
    row.innerHTML = `<input type="text" placeholder="https://t.me/channel/123" value="${url ? escapeHtml(String(url)) : ''}" style="flex:3;padding:6px 8px;background:var(--bg-dark);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);font-size:0.83rem;box-sizing:border-box;" class="bc-pl-url"><input type="number" placeholder="Лимит" value="${limit ? limit : ''}" min="0" style="width:80px;padding:6px 8px;background:var(--bg-dark);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);font-size:0.83rem;box-sizing:border-box;" class="bc-pl-limit" title="0 = без лимита"><button type="button" onclick="this.parentElement.remove()" style="padding:4px 8px;background:none;border:1px solid var(--error);border-radius:4px;color:var(--error);cursor:pointer;font-size:0.8rem;flex-shrink:0;">✕</button>`;
    container.appendChild(row);
}

function getPostLinksData() {
    const rows = document.querySelectorAll('.bc-post-link-row');
    const links = [];
    rows.forEach(row => {
        const url = row.querySelector('.bc-pl-url').value.trim();
        const rawLimit = parseInt(row.querySelector('.bc-pl-limit').value);
        const limit = (isNaN(rawLimit) || rawLimit < 0) ? 0 : rawLimit;
        if (url) links.push({ url: url, limit: limit, sent: 0 });
    });
    return links;
}

function setBroadcastSource(source) {
    broadcastSource = source;
    ['parsed', 'file', 'manual', 'phones'].forEach(s => {
        const btn = document.getElementById('bcSrc' + s.charAt(0).toUpperCase() + s.slice(1));
        if (!btn) return;
        btn.style.borderColor = s === source ? 'var(--primary)' : 'var(--border)';
        btn.style.background = s === source ? 'var(--primary)' : 'var(--bg-hover)';
        btn.style.color = s === source ? '#000' : 'var(--text-primary)';
    });
    document.getElementById('bcParsedBlock').style.display = source === 'parsed' ? 'block' : 'none';
    document.getElementById('bcFileBlock').style.display = source === 'file' ? 'block' : 'none';
    document.getElementById('bcManualBlock').style.display = source === 'manual' ? 'block' : 'none';
    document.getElementById('bcPhonesBlock').style.display = source === 'phones' ? 'block' : 'none';
}

function setBcPhonesMode(mode) {
    document.getElementById('bcPhonesTextBlock').style.display = mode === 'text' ? 'block' : 'none';
    document.getElementById('bcPhonesFileBlock').style.display = mode === 'file' ? 'block' : 'none';
    const btnText = document.getElementById('bcPhonesModeText');
    const btnFile = document.getElementById('bcPhonesModeFile');
    btnText.style.borderColor = mode === 'text' ? 'var(--primary)' : 'var(--border)';
    btnText.style.background = mode === 'text' ? 'var(--primary)' : 'var(--bg-hover)';
    btnText.style.color = mode === 'text' ? '#000' : 'var(--text-primary)';
    btnFile.style.borderColor = mode === 'file' ? 'var(--primary)' : 'var(--border)';
    btnFile.style.background = mode === 'file' ? 'var(--primary)' : 'var(--bg-hover)';
    btnFile.style.color = mode === 'file' ? '#000' : 'var(--text-primary)';
    bcPhonesMode = mode;
}


async function loadBroadcastParserBases() {
    const select = document.getElementById('bcParsedBase');
    const multi = document.getElementById('bcParsedMulti');
    try {
        const res = await fetch(`${API_URL}/broadcast/parser-bases`, {
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (!res.ok) throw new Error('Ошибка');
        const data = await res.json();
        select.innerHTML = '<option value="">-- Выберите базу --</option>';
        (data.bases || []).forEach(b => {
            select.innerHTML += `<option value="${b.id}">${escapeHtml(b.group_title || b.group_link)} (${b.total_users} чел.)</option>`;
        });
        if (multi) {
            multi.innerHTML = (data.bases || []).map(b =>
                `<label class="bc-parsed-item">
                    <input type="checkbox" class="bc-parsed-chk" value="${b.id}" onchange="updateBcParsedMultiCount()">
                    <span class="bc-parsed-name">${escapeHtml(b.group_title || b.group_link)}</span>
                    <span class="bc-parsed-meta">${b.total_users} чел.</span>
                </label>`
            ).join('') || '<div style="color:var(--text-secondary);font-size:0.8rem;padding:6px;">Баз нет</div>';
        }
    } catch (e) {
        select.innerHTML = '<option value="">Ошибка загрузки</option>';
        if (multi) multi.innerHTML = '';
    }
}

function toggleBcParsedMulti() {
    const block = document.getElementById('bcParsedMultiBlock');
    const single = document.getElementById('bcParsedSingleBlock');
    if (!block) return;
    const show = block.hidden;
    block.hidden = !show;
    if (single) single.hidden = show;  // прячем одиночный select когда включён мультивыбор
    if (!show) {
        // выключаем мультивыбор → сбрасываем чекбоксы
        document.querySelectorAll('#bcParsedMulti .bc-parsed-chk:checked').forEach(c => { c.checked = false; });
        updateBcParsedMultiCount();
    }
}

let _bcParsedPreviewTimer = null;
function updateBcParsedMultiCount() {
    const ids = Array.from(document.querySelectorAll('#bcParsedMulti .bc-parsed-chk:checked'))
        .map(c => parseInt(c.value)).filter(Boolean);
    const lbl = document.getElementById('bcParsedMultiCount');
    if (!lbl) return;
    if (!ids.length) { lbl.textContent = 'Ни одной базы не выбрано'; return; }
    lbl.textContent = `Выбрано баз: ${ids.length} · считаю получателей…`;
    clearTimeout(_bcParsedPreviewTimer);
    _bcParsedPreviewTimer = setTimeout(async () => {
        try {
            const res = await fetch(`${API_URL}/broadcast/bases/preview-count`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` },
                body: JSON.stringify({ base_ids: ids }),
            });
            const d = await res.json();
            if (res.ok) lbl.textContent = `Выбрано баз: ${d.bases} · получателей: ${d.recipients} (без дублей)`;
        } catch (_) {
            lbl.textContent = `Выбрано баз: ${ids.length}`;
        }
    }, 350);
}

async function loadContactsBase() {
    const info = document.getElementById('bcContactsInfo');
    const btn = document.getElementById('bcContactsLoadBtn');
    const selected = bcAccountPicker ? Array.from(bcAccountPicker.getSelected()) : [];
    if (!selected.length) {
        if (info) { info.style.color = '#fca5a5'; info.textContent = 'Сначала отметь аккаунт в списке ниже.'; }
        return;
    }
    const accId = selected[0];
    const acc = (bcAccountPicker.accounts || []).find(a => String(a.id) === String(accId));
    const accLabel = acc ? (acc.username ? '@' + String(acc.username).replace(/^@/, '') : (acc.display_name || acc.phone || ('#' + accId))) : ('#' + accId);
    if (selected.length > 1 && info) {
        info.style.color = 'var(--text-secondary)';
    }
    if (btn) { btn.disabled = true; btn.textContent = '⏳ Гружу контакты…'; }
    if (info) { info.style.color = 'var(--text-secondary)'; info.textContent = `Поднимаю ${accLabel} и тяну контакты…`; }
    try {
        const res = await fetch(`${API_URL}/broadcast/contacts-base`, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${localStorage.getItem('token')}`,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ account_id: accId }),
        });
        const data = await res.json();
        if (!res.ok) throw new Error(data.detail || 'Не удалось загрузить контакты');
        await loadBroadcastParserBases();
        const select = document.getElementById('bcParsedBase');
        if (select && data.base_id) select.value = String(data.base_id);
        if (typeof setBroadcastSource === 'function') setBroadcastSource('parsed');
        const st = data.stats || {};
        if (info) {
            info.style.color = 'var(--primary)';
            let txt = `Готово (${data.account_label || accLabel}): ${data.count} получателей — по @username: ${st.username || 0}, по номеру: ${st.phone || 0}, пропущено: ${st.skipped || 0}. База выбрана.`;
            const sk = data.skipped_labels || [];
            if (sk.length) {
                txt += ` Пропущены (нет @username и скрыт номер): ${sk.slice(0, 10).join(', ')}${sk.length > 10 ? '…' : ''}.`;
            }
            info.textContent = txt;
        }
        if (typeof showNotification === 'function') showNotification(`Контактов в базе: ${data.count}`, 'success');
    } catch (e) {
        if (info) { info.style.color = '#fca5a5'; info.textContent = e.message || 'Ошибка'; }
        if (typeof showNotification === 'function') showNotification(e.message || 'Ошибка загрузки контактов', 'error');
    } finally {
        if (btn) { btn.disabled = false; btn.textContent = '📇 Создать базу из контактов выбранного аккаунта'; }
    }
}

async function saveCurrentAsBase(kind) {
    const raw = kind === 'phones'
        ? (document.getElementById('bcPhonesManual')?.value || '')
        : (document.getElementById('bcManualUsers')?.value || '');
    const lines = raw.split('\n').map(l => l.trim()).filter(l =>
        l && !l.startsWith('(номера уже') && !l.startsWith('(получатели уже') && !l.startsWith('(оставьте'));
    if (!lines.length) {
        showNotification('Сначала введите получателей', 'error');
        return;
    }
    const today = new Date().toLocaleDateString('ru-RU');
    const suggested = (kind === 'phones' ? 'Номера ' : 'Список ') + today;
    const name = prompt('Название базы:', suggested);
    if (name === null) return;
    const nm = name.trim();
    if (!nm) { showNotification('Укажите название базы', 'error'); return; }
    try {
        const res = await fetch(`${API_URL}/broadcast/bases`, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${localStorage.getItem('token')}`,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                name: nm,
                targets: lines.join('\n'),
                source: kind === 'phones' ? 'phones' : 'manual',
            }),
        });
        const data = await res.json();
        if (!res.ok) throw new Error(data.detail || 'Не удалось сохранить базу');
        showNotification(`База сохранена: ${data.count} получателей. Доступна во вкладке «Базы».`, 'success');
        await loadBroadcastParserBases();
    } catch (e) {
        showNotification(e.message || 'Ошибка сохранения базы', 'error');
    }
}

async function checkPhoneTarget() {
    const input = document.getElementById('bcPhoneCheckInput');
    const info = document.getElementById('bcPhoneCheckInfo');
    const phone = (input?.value || '').trim();
    if (!phone) { if (info) { info.style.color = '#fca5a5'; info.textContent = 'Введите номер'; } return; }
    const selected = bcAccountPicker ? Array.from(bcAccountPicker.getSelected()) : [];
    if (!selected.length) { if (info) { info.style.color = '#fca5a5'; info.textContent = 'Отметь аккаунт в списке ниже — с него проверим'; } return; }
    const accId = selected[0];
    if (info) { info.style.color = 'var(--text-secondary)'; info.textContent = 'Проверяю через Telegram…'; }
    try {
        const res = await fetch(`${API_URL}/broadcast/phone-check`, {
            method: 'POST',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}`, 'Content-Type': 'application/json' },
            body: JSON.stringify({ account_id: accId, phone }),
        });
        const data = await res.json();
        if (!res.ok) throw new Error(data.detail || 'Ошибка проверки');
        if (info) {
            if (data.resolved) {
                info.style.color = 'var(--primary)';
                const who = data.username ? '@' + data.username : (data.name || ('id ' + data.user_id));
                info.textContent = `✅ ${data.phone} — можно писать (${who})`;
            } else {
                info.style.color = '#ffa94d';
                info.textContent = `⛔ ${data.phone} — нельзя: ${data.reason || 'не резолвится'}`;
            }
        }
    } catch (e) {
        if (info) { info.style.color = '#fca5a5'; info.textContent = e.message || 'Ошибка'; }
    }
}

async function loadBroadcastAccounts() {
    const container = document.getElementById('bcAccountPickerContainer');
    try {
        const res = await fetch(`${API_URL}/accounts/for-warmup?has_session=true`, {
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (!res.ok) throw new Error('Ошибка загрузки аккаунтов');
        const data = await res.json();
        const accounts = data.accounts || [];

        if (bcAccountPicker) bcAccountPicker.destroy();
        bcAccountPicker = new AccountPicker({
            containerId: 'bcAccountPickerContainer',
            mode: 'multi',
            showProjectColumn: true,
            perPage: 20,
            accentColor: 'var(--primary)',
            customIsDisabled: (acc) => {
                if (acc.busy_in === 'parsing' || acc.in_parsing) return true;
                if (acc.busy_in === 'sending' || acc.in_broadcast) return true;
                if (acc.broadcast_blocked || acc.broadcast_usable === false) return true;
                const tags = acc.filter_tags || [];
                if (tags.some(t => ['dead', 'blocked', 'frozen'].includes(t))) return true;
                return !acc.has_session;
            },
            onSelectionChange: (selected, count) => {
                // noop — selection is read on submit
            }
        });

        const projRes = await fetch(`${API_URL}/projects`, {
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (projRes.ok) {
            const projData = await projRes.json();
            bcAccountPicker.setProjects((projData.projects || projData || []).map(p => ({id: p.id, name: p.name})));
        }
        bcAccountPicker.setAccounts(accounts);
    } catch (e) {
        container.innerHTML = `<div style="text-align:center;color:var(--error);font-size:0.8rem;padding:10px;">${escapeHtml(e.message)}</div>`;
    }
}

async function handleBroadcastFileUpload() {
    const file = document.getElementById('bcFileInput').files[0];
    if (!file) return;
    const info = document.getElementById('bcFileInfo');
    info.textContent = 'Загрузка...';

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

    try {
        const res = await fetch(`${API_URL}/broadcast/upload-base`, {
            method: 'POST',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` },
            body: formData
        });
        if (!res.ok) {
            const err = await res.json();
            throw new Error(err.detail || 'Ошибка');
        }
        const data = await res.json();
        broadcastFileUsers = data.users;
        info.textContent = `✅ Загружено ${data.count} получателей`;
    } catch (e) {
        info.textContent = `❌ ${e.message}`;
        broadcastFileUsers = null;
    }
}

async function handleBroadcastPhonesFileUpload() {
    const file = document.getElementById('bcPhonesFileInput').files[0];
    if (!file) return;
    const info = document.getElementById('bcPhonesFileInfo');
    info.textContent = 'Загрузка...';

    const formData = new FormData();
    formData.append('file', file);
    formData.append('source', 'phones');

    try {
        const res = await fetch(`${API_URL}/broadcast/upload-base`, {
            method: 'POST',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` },
            body: formData
        });
        if (!res.ok) {
            const err = await res.json();
            throw new Error(err.detail || 'Ошибка');
        }
        const data = await res.json();
        broadcastPhonesFileUsers = data.users;
        let statsText = `✅ Загружено ${data.count} записей`;
        if (data.phones_count) statsText += ` (📱 ${data.phones_count} номеров`;
        if (data.usernames_count) statsText += `, @${data.usernames_count} username`;
        if (data.ids_count) statsText += `, 🆔 ${data.ids_count} ID`;
        if (data.phones_count || data.usernames_count || data.ids_count) statsText += ')';
        info.textContent = statsText;
    } catch (e) {
        info.textContent = `❌ ${e.message}`;
        broadcastPhonesFileUsers = null;
    }
}

function updateBcPhonesManualCount() {
    const textarea = document.getElementById('bcPhonesManual');
    const countDiv = document.getElementById('bcPhonesManualCount');
    if (!textarea || !countDiv) return;
    const lines = textarea.value.trim().split('\n').filter(l => l.trim()).length;
    countDiv.textContent = lines ? `✓ ${lines} номеров введено` : '';
}

function parsePhoneLines(text) {
    const lines = text.trim().split('\n').filter(l => l.trim());
    return lines.map(line => {
        let cleaned = line.trim().replace(/[\s\-\(\)]+/g, '');
        if (/^\+?\d{10,15}$/.test(cleaned)) {
            if (cleaned.startsWith('8') && cleaned.length === 11) {
                cleaned = '+7' + cleaned.substring(1);
            } else if (!cleaned.startsWith('+') && cleaned.length === 10) {
                cleaned = '+7' + cleaned;
            } else if (!cleaned.startsWith('+')) {
                cleaned = '+' + cleaned;
            }
            return { phone_number: cleaned };
        }
        return null;
    }).filter(Boolean);
}

async function createBroadcastJob() {
    const accountIds = bcAccountPicker ? Array.from(bcAccountPicker.getSelected()) : [];
    if (!accountIds.length) return showNotification('Выберите аккаунты', 'error');

    const body = {
        name: document.getElementById('bcName').value || null,
        content_type: broadcastContentType,
        spintax_enabled: false,
        delete_after_send: document.getElementById('bcDeleteAfterSend').checked,
        silent_send: document.getElementById('bcSilentSend')?.checked || false,
        hide_author: document.getElementById('bcHideAuthor')?.checked || false,
        pause_min: parseInt(document.getElementById('bcPauseMin').value) || 15,
        pause_max: parseInt(document.getElementById('bcPauseMax').value) || 30,
        messages_per_account: parseInt(document.getElementById('bcMsgPerAccount')?.value || '0') || 0,
        batch_size: 1,
        parallel_workers: parseInt(document.getElementById('bcParallel')?.value || '10') || 10,
        daily_limit: parseInt(document.getElementById('bcDailyLimit').value) || 0,
        auto_stop_percent: parseInt(document.getElementById('bcAutoStop').value) || 0,
        account_ids: accountIds,
        autoreply_enabled: document.getElementById('bcAutoreplyEnabled')?.checked || false,
        autoreply_template_id: _bcAutoreplyTemplateId(),
    };

    if (broadcastContentType === 'text') {
        body.message_text = _broadcastTextForSave();
        if (!body.message_text.trim()) return showNotification('Введите текст сообщения', 'error');
        body.spintax_enabled = _broadcastShouldRandomize(body.message_text);
    } else {
        const postLinks = getPostLinksData();
        if (!postLinks.length) return showNotification('Добавьте хотя бы одну ссылку на пост', 'error');
        body.post_links = postLinks;
        body.post_link = postLinks[0].url;
    }

    if (broadcastSource === 'parsed') {
        const multi = Array.from(document.querySelectorAll('#bcParsedMulti .bc-parsed-chk:checked'))
            .map(c => parseInt(c.value)).filter(Boolean);
        if (multi.length) {
            body.parsed_base_ids = multi;
        } else {
            const baseId = document.getElementById('bcParsedBase').value;
            if (!baseId) return showNotification('Выберите базу получателей', 'error');
            body.parsed_base_id = parseInt(baseId);
        }
    } else if (broadcastSource === 'file') {
        if (!broadcastFileUsers || !broadcastFileUsers.length) return showNotification('Загрузите файл с получателями', 'error');
        body.users_data = broadcastFileUsers;
    } else if (broadcastSource === 'phones') {
        if (bcPhonesMode === 'file') {
            if (!broadcastPhonesFileUsers || !broadcastPhonesFileUsers.length) return showNotification('Загрузите файл с номерами', 'error');
            body.users_data = broadcastPhonesFileUsers;
        } else {
            const phoneUsers = parsePhoneLines(document.getElementById('bcPhonesManual').value);
            if (!phoneUsers.length) return showNotification('Введите номера телефонов', 'error');
            body.users_data = phoneUsers;
        }
    } else {
        const lines = document.getElementById('bcManualUsers').value.trim().split('\n').filter(l => l.trim());
        if (!lines.length) return showNotification('Введите получателей', 'error');
        body.users_data = lines.map(line => {
            line = line.trim().replace(/^@/, '');
            return /^\d+$/.test(line) ? { user_id: parseInt(line) } : { username: line };
        });
    }

    try {
        const res = await fetch(`${API_URL}/broadcast/create`, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${localStorage.getItem('token')}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(body)
        });
        const raw = await res.text();
        let data = {};
        try { data = raw ? JSON.parse(raw) : {}; } catch (_) {}
        if (!res.ok) {
            const msg = data.detail || data.message || raw || `HTTP ${res.status}`;
            throw new Error(typeof msg === 'string' ? msg : JSON.stringify(msg));
        }
        showNotification(`✅ ${data.message || 'Задача создана'}`, 'success');
        if (data.pool_warning) showNotification(`⚠ ${data.pool_warning}`, 'warning');
        // Сохраняем выбранный движок ИИ-агента для созданной задачи (воронка/простой).
        const agentMode = document.getElementById('bcAgentMode')?.value === 'funnel' ? 'funnel' : 'simple';
        const newJobId = data.job_id || data.id;
        if (newJobId) {
            try {
                await fetch(`${API_URL}/broadcast/jobs/${newJobId}/agent-config`, {
                    method: 'POST',
                    headers: {
                        'Authorization': `Bearer ${localStorage.getItem('token')}`,
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ agent_mode: agentMode }),
                });
            } catch (_) { /* режим можно сменить позже во вкладке */ }
        }
        closeBroadcastModal();
        loadBroadcastJobs();
    } catch (e) {
        showNotification(e.message || 'Ошибка создания задачи', 'error');
    }
}


// ── Быстрые закрепляемые чипы статусов (как в «Клиентах») ──────────────────
// Сводка ВСЕГДА считается по всем задачам (_bcAllJobs), фильтр карточек —
// client-side. Так «Всего» показывает реальное число при любом активном фильтре.
const BC_QUICK_CATALOG = [
    { value: '', label: 'Все' },
    { value: 'running', label: 'Запущено' },
    { value: 'draft', label: 'Черновики' },
    { value: 'waiting', label: 'Ожидание' },
    { value: 'paused', label: 'Пауза' },
    { value: 'completed', label: 'Завершено' },
    { value: 'cancelled', label: 'Остановлено' },
    { value: 'failed', label: 'Ошибки' },
];
const BC_QUICK_DEFAULT = ['', 'running', 'draft', 'completed'];
const BC_QUICK_PINS_KEY = 'bcStatusQuickPins';

function bcJobMatches(job, v) {
    if (!v) return true;
    const s = job.status;
    switch (v) {
        case 'running': return s === 'running';
        case 'draft': return s === 'draft' || s === 'pending';
        case 'waiting': return s === 'waiting';
        case 'paused': return s === 'paused';
        case 'completed': return s === 'completed';
        case 'cancelled': return s === 'cancelled' || s === 'stopped';
        case 'failed': return s === 'failed';
        default: return s === v;
    }
}

function bcQuickCount(v, jobs) {
    return (jobs || []).filter(j => bcJobMatches(j, v)).length;
}

function _bcLoadPins() {
    try {
        const raw = JSON.parse(localStorage.getItem(BC_QUICK_PINS_KEY) || 'null');
        if (!Array.isArray(raw)) return [...BC_QUICK_DEFAULT];
        const valid = new Set(BC_QUICK_CATALOG.map(o => o.value));
        const out = raw.map(s => String(s)).filter(s => valid.has(s));
        return out.length ? out : [...BC_QUICK_DEFAULT];
    } catch (_) {
        return [...BC_QUICK_DEFAULT];
    }
}

function _bcSavePins(pins) {
    try { localStorage.setItem(BC_QUICK_PINS_KEY, JSON.stringify(pins)); } catch (_) { /* ignore */ }
}

function renderBroadcastQuick() {
    const el = document.getElementById('broadcastStatusQuick');
    if (!el) return;
    if (!_bcAllJobs.length) { el.hidden = true; return; }
    el.hidden = false;
    const active = document.getElementById('broadcastFilterStatus')?.value || '';
    const pins = _bcLoadPins();
    const map = new Map(BC_QUICK_CATALOG.map(o => [o.value, o]));
    const chips = pins.map(v => {
        const meta = map.get(v);
        if (!meta) return '';
        const cnt = bcQuickCount(v, _bcAllJobs);
        const cls = active === v ? ' active' : '';
        return `<button type="button" class="client-st-quick${cls}" data-bcq="${escapeHtml(v)}" title="${escapeHtml(meta.label)}">${escapeHtml(meta.label)} ${cnt}</button>`;
    }).join('');
    el.innerHTML = chips +
        '<button type="button" class="client-st-pin-btn" data-bcpin="add" title="Закрепить статус">+</button>' +
        '<button type="button" class="client-st-pin-btn" data-bcpin="remove" title="Открепить статус">−</button>';

    el.querySelectorAll('.client-st-quick').forEach(btn => {
        btn.onclick = () => {
            const sel = document.getElementById('broadcastFilterStatus');
            if (sel) sel.value = btn.dataset.bcq || '';
            loadBroadcastJobs();
        };
    });
    el.querySelectorAll('.client-st-pin-btn').forEach(btn => {
        btn.onclick = (e) => {
            e.stopPropagation();
            const menu = document.getElementById('broadcastStatusPinMenu');
            if (menu && !menu.hidden && menu.dataset.anchorMode === btn.dataset.bcpin) {
                _bcClosePinMenu();
                return;
            }
            _bcOpenPinMenu(btn.dataset.bcpin, btn);
        };
    });
}

let _bcPinMenuCloser = null;

function _bcClosePinMenu() {
    const menu = document.getElementById('broadcastStatusPinMenu');
    if (menu) { menu.hidden = true; menu.dataset.anchorMode = ''; }
    if (_bcPinMenuCloser) {
        document.removeEventListener('click', _bcPinMenuCloser, true);
        _bcPinMenuCloser = null;
    }
}

function _bcOpenPinMenu(mode, anchorEl) {
    _bcClosePinMenu();
    const menu = document.getElementById('broadcastStatusPinMenu');
    if (!menu) return;
    const pins = _bcLoadPins();
    const map = new Map(BC_QUICK_CATALOG.map(o => [o.value, o]));
    const items = mode === 'add'
        ? BC_QUICK_CATALOG.filter(o => !pins.includes(o.value))
        : pins.map(v => map.get(v)).filter(Boolean);

    if (!items.length) {
        menu.innerHTML = `<div class="client-st-pin-menu-empty">${mode === 'add' ? 'Все статусы уже закреплены' : 'Нет закреплённых статусов'}</div>`;
    } else {
        menu.innerHTML = items.map(o => {
            const cnt = bcQuickCount(o.value, _bcAllJobs);
            const prefix = mode === 'remove' ? '− ' : '';
            return `<button type="button" class="client-st-pin-menu-item" data-bcq="${escapeHtml(o.value)}">${prefix}${escapeHtml(o.label)} <span class="hint">${cnt}</span></button>`;
        }).join('');
    }

    menu.hidden = false;
    menu.dataset.anchorMode = mode;
    const rect = anchorEl.getBoundingClientRect();
    menu.style.left = `${Math.max(8, rect.left)}px`;
    menu.style.top = `${rect.bottom + 4}px`;

    menu.querySelectorAll('.client-st-pin-menu-item').forEach(btn => {
        btn.onclick = (e) => {
            e.stopPropagation();
            const v = btn.dataset.bcq ?? '';
            const now = _bcLoadPins();
            if (mode === 'add') {
                if (!now.includes(v)) _bcSavePins([...now, v]);
            } else {
                _bcSavePins(now.filter(p => p !== v));
                const sel = document.getElementById('broadcastFilterStatus');
                if (sel && sel.value === v) {
                    sel.value = '';
                    _bcClosePinMenu();
                    loadBroadcastJobs();
                    return;
                }
            }
            _bcClosePinMenu();
            renderBroadcastQuick();
        };
    });

    _bcPinMenuCloser = (e) => {
        if (!menu.contains(e.target) && e.target !== anchorEl && !anchorEl.contains(e.target)) {
            _bcClosePinMenu();
        }
    };
    setTimeout(() => document.addEventListener('click', _bcPinMenuCloser, true), 0);
}

async function loadBroadcastJobs() {
    const container = document.getElementById('broadcastJobsList');
    if (!container) return;

    const reqId = ++_broadcastJobsReqId;
    try {
        // Грузим ВСЕ задачи всегда — фильтр статуса и поиск применяем client-side,
        // чтобы сводка-чипы («Всего» и т.п.) считались по полному списку.
        const res = await fetch(`${API_URL}/broadcast/jobs`, {
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (!res.ok) throw new Error('Ошибка загрузки');
        const data = await res.json();
        if (reqId !== _broadcastJobsReqId) return;

        _bcAllJobs = data.jobs || [];
        renderBroadcastQuick();
        adjustBroadcastListRefresh(_bcAllJobs);

        if (!_bcAllJobs.length) {
            container.innerHTML = `
                <div style="padding:40px;text-align:center;color:var(--text-secondary);">
                    <p style="font-size:1.5rem;margin-bottom:8px;">📢</p>
                    <p>Нет задач рассылки</p>
                    <p style="font-size:0.8rem;margin-top:8px;">Нажмите "Создать задачу"</p>
                </div>`;
            return;
        }

        const status = document.getElementById('broadcastFilterStatus')?.value || '';
        const q = (document.getElementById('broadcastSearch')?.value || '').trim().toLowerCase();
        let jobsToShow = _bcAllJobs.filter(j => bcJobMatches(j, status));
        if (q) {
            jobsToShow = jobsToShow.filter(j =>
                (j.name || '').toLowerCase().includes(q) || String(j.id).includes(q)
            );
        }

        if (!jobsToShow.length) {
            const reason = q
                ? `Ничего не найдено по запросу «${escapeHtml(q)}»`
                : `С выбранным фильтром задач нет, но всего: <b>${_bcAllJobs.length}</b>`;
            container.innerHTML = `
                <div style="padding:30px;text-align:center;color:var(--text-secondary);">
                    <p>${reason}</p>
                    <button onclick="document.getElementById('broadcastFilterStatus').value='';document.getElementById('broadcastSearch').value='';loadBroadcastJobs();" style="margin-top:10px;padding:8px 14px;background:var(--primary);border:none;border-radius:6px;color:#000;cursor:pointer;font-size:0.8rem;">Показать все задачи</button>
                </div>`;
        } else {
            container.innerHTML = jobsToShow.map(renderBroadcastJobCard).join('');
        }
    } catch (err) {
        if (reqId !== _broadcastJobsReqId) return;
        container.innerHTML = `<div style="padding:20px;text-align:center;color:var(--error);">Ошибка: ${escapeHtml(err.message)}</div>`;
    }
}

function broadcastJobIsLive(status) {
    return status === 'running' || status === 'waiting';
}

function broadcastJobCardButtons(job) {
    const id = job.id;
    if (job.status === 'running') {
        return `
            <button onclick="event.stopPropagation();pauseBroadcastJob(${id})" class="bc-card-btn" title="Пауза">${_bcIcon('pause')}<span>Пауза</span></button>
            <button onclick="event.stopPropagation();stopBroadcastJob(${id})" class="bc-card-btn bc-card-btn--danger" title="Стоп">${_bcIcon('stop')}<span>Стоп</span></button>`;
    }
    if (job.status === 'waiting') {
        return `
            <span class="bc-card-btn" style="opacity:0.55;cursor:default;" title="Задача уже запускается">${_bcIcon('timer')}<span>Запуск…</span></span>
            <button onclick="event.stopPropagation();stopBroadcastJob(${id}, true)" class="bc-card-btn bc-card-btn--danger" title="Отмена">${_bcIcon('stop')}<span>Отмена</span></button>`;
    }
    return `
        <button onclick="event.stopPropagation();editBroadcastJob(${id})" class="bc-card-btn bc-card-btn--icon" title="Редактировать">${_bcIcon('edit')}</button>
        <button onclick="event.stopPropagation();startBroadcastJob(${id})" class="bc-card-btn bc-card-btn--primary" title="Старт">${_bcIcon('play')}<span>Старт</span></button>
        <button onclick="event.stopPropagation();deleteBroadcastJob(${id})" class="bc-card-btn bc-card-btn--icon" title="Удалить">${_bcIcon('trash')}</button>`;
}

function broadcastJobDetailButtons(job, jid) {
    if (job.status === 'running') {
        return `
            <button onclick="pauseBroadcastJob(${jid})" class="bcd-btn bcd-btn--warn" style="flex:1;justify-content:center;min-width:120px;">⏸ Пауза</button>
            <button onclick="stopBroadcastJob(${jid})" class="bcd-btn bcd-btn--stop">⏹ Стоп</button>`;
    }
    if (job.status === 'waiting') {
        return `
            <span class="bcd-btn bcd-btn--wait">⏳ Запуск…</span>
            <button onclick="stopBroadcastJob(${jid}, true)" class="bcd-btn bcd-btn--stop">⏹ Отмена</button>`;
    }
    return `
        <button onclick="startBroadcastJob(${jid})" class="bcd-btn bcd-btn--start">▶ Старт</button>
        <button onclick="closeBroadcastDetails();editBroadcastJob(${jid});" class="bcd-btn" title="Редактировать задачу">✏️ Ред.</button>
        <button onclick="retryBroadcastFailed(${jid})" class="bcd-btn" title="Вернуть получателей с ошибкой обратно в очередь">🔁 Retry</button>
        <button onclick="resetBroadcastLimits(${jid})" class="bcd-btn" title="Сбросить дневные лимиты — получатели НЕ затрагиваются">🔄 Лимиты</button>
        <button onclick="resetBroadcastJob(${jid})" class="bcd-btn bcd-btn--warn" title="Сбросить ВСЁ: счётчики, логи и статусы получателей">♻️ Сброс</button>
        <span class="bcd-actions-spacer"></span>
        <button onclick="deleteBroadcastJob(${jid});closeBroadcastDetails();" class="bcd-btn bcd-btn--danger" title="Удалить задачу">🗑 Удалить</button>`;
}

function _bcIcon(name){
  const map = {
    chat: '<path d="m6 18l-2.3 2.3q-.475.475-1.088.213T2 19.575V4q0-.825.588-1.412T4 2h16q.825 0 1.413.588T22 4v12q0 .825-.587 1.413T20 18z"/>',
    post: '<path d="M20 12q-.425 0-.712-.288T19 11V4h-7q-.425 0-.712-.288T11 3t.288-.712T12 2h7q.825 0 1.413.588T21 4v7q0 .425-.288.713T20 12M4 22q-.825 0-1.412-.587T2 20V8q0-.825.588-1.412T4 6h7q.825 0 1.413.588T13 8v12q0 .825-.587 1.413T11 22z"/>',
    users: '<path d="M1 18q-.425 0-.712-.288T0 17v-.575q0-1.075 1.1-1.75T4 14q.325 0 .625.013t.575.062q-.35.525-.525 1.1t-.175 1.2V18zm6 0q-.425 0-.712-.288T6 17v-.625q0-.8.438-1.463t1.237-1.162T9.588 13T12 12.75q1.325 0 2.438.25t1.912.75t1.225 1.163t.425 1.462V17q0 .425-.287.713T17 18zm12.5 0v-1.625q0-.65-.162-1.225t-.488-1.075q.275-.05.563-.062T20 14q1.8 0 2.9.663t1.1 1.762V17q0 .425-.288.713T23 18zM4 13q-.825 0-1.412-.587T2 11q0-.85.588-1.425T4 9q.85 0 1.425.575T6 11q0 .825-.575 1.413T4 13m16 0q-.825 0-1.412-.587T18 11q0-.85.588-1.425T20 9q.85 0 1.425.575T22 11q0 .825-.575 1.413T20 13m-8-1q-1.25 0-2.125-.875T9 9q0-1.275.875-2.137T12 6q1.275 0 2.138.863T15 9q0 1.25-.862 2.125T12 12"/>',
    target: '<path d="M19 13q-.425 0-.712-.288T18 12q0-2.5-1.75-4.25T12 6q-2.5 0-4.25 1.75T6 12q0 2.5 1.75 4.25T12 18q.425 0 .713.288T13 19t-.288.713T12 20q-1.65 0-3.1-.625t-2.55-1.725T4.625 15.1T4 12t.625-3.1t1.725-2.55T8.9 4.625T12 4t3.1.625t2.55 1.725T19.375 8.9T20 12q0 .425-.288.713T19 13m-7 4q-2.075 0-3.537-1.463T7 12t1.463-3.537T12 7t3.538 1.463T17 12t-1.463 3.538T12 17m0-2q1.25 0 2.125-.875T15 12t-.875-2.125T12 9t-2.125.875T9 12t.875 2.125T12 15"/>',
    check: '<path d="M9 16.17L4.83 12l-1.42 1.41L9 19L21 7l-1.41-1.41z"/>',
    cross: '<path d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z"/>',
    skip: '<path d="M5 19q-.425 0-.712-.288T4 18V6q0-.425.288-.712T5 5q.4 0 .7.288T6 6v5L13.3 5.4q.5-.375 1.1-.087t.6.887v11.6q0 .6-.6.888T13.3 18.6L6 13v5q0 .425-.288.713T5 19m13-1V6q0-.425.288-.712T19 5t.713.288T20 6v12q0 .425-.288.713T19 19t-.712-.288"/>',
    chart: '<path d="M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm2-4h4V8H7zm6 0h4v-4h-4zm0-6h4V8h-4z"/>',
    trash: '<path d="M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21zM17 6H7v13h10zM9 17h2V8H9zm4 0h2V8h-2z"/>',
    edit: '<path d="M5 21q-.425 0-.712-.288T4 20v-2.825q0-.2.075-.388t.225-.337l9.6-9.6l4.25 4.25l-9.6 9.6q-.15.15-.337.225t-.388.075zm14.075-12.225l-4.25-4.25l1.4-1.4q.575-.575 1.413-.575t1.412.575l1.425 1.425q.575.575.575 1.413t-.575 1.412z"/>',
    play: '<path d="M9.5 16.5v-9q0-.425.288-.712T10.5 6.5q.15 0 .313.05t.312.15l7 4.5q.275.2.4.45t.125.55t-.125.55t-.4.45l-7 4.5q-.15.1-.312.15T10.5 17.5q-.425 0-.712-.288T9.5 16.5"/>',
    pause: '<path d="M14 19V5h4v14zm-8 0V5h4v14z"/>',
    stop: '<path d="M6 18V6h12v12z"/>',
    timer: '<path d="M9 3V1h6v2zm2 11h2V8h-2zm1 8q-1.875 0-3.512-.712T5.625 19.375T3.713 16.512T3 13t.713-3.512T5.625 6.625T8.488 4.713T12 4q1.575 0 3.025.5t2.7 1.45L19.15 4.5L20.55 5.9L19.125 7.325Q20 8.45 20.5 9.85T21 13q0 1.875-.713 3.513t-1.912 2.85t-2.863 1.925T12 22"/>',
  };
  return `<svg class="bc-ico" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">${map[name]||''}</svg>`;
}

function renderBroadcastJobCard(job) {
    const statusKeys = { 'draft':'draft','pending':'pending','waiting':'waiting','running':'running','paused':'paused','completed':'done','failed':'fail','cancelled':'fail','stopped':'fail' };
    const statusTexts = { 'draft': 'Черновик', 'pending': 'Ожидание', 'waiting': 'Загрузка акк.', 'running': 'Выполняется', 'paused': 'Пауза', 'completed': 'Завершено', 'failed': 'Ошибка', 'cancelled': 'Остановлено', 'stopped': 'Остановлено' };

    const contentIcon = _bcIcon(job.content_type === 'post' ? 'post' : 'chat');
    const deleteIcon = job.delete_after_send ? ' <span class="bc-meta-del" title="Удалять после отправки">🗑</span>' : '';
    const buttons = broadcastJobCardButtons(job);
    const statusLabel = job.display_label || statusTexts[job.status] || job.status;
    // Цвет бейджа — из вычисленного бэкендом display_status (там цвет и подпись
    // согласованы), иначе «Завершена» красилась оранжевым по сырому paused.
    const badgeByDisplay = {
        'draft':'draft', 'pending':'pending', 'waiting':'waiting', 'waiting_accounts':'waiting',
        'running':'running', 'running_busy':'running', 'paused':'paused',
        'completed':'done', 'failed':'fail', 'cancelled':'fail', 'stopped':'fail',
    };
    const statusKey = badgeByDisplay[job.display_status] || statusKeys[job.status] || 'draft';

    const progress = job.total_recipients > 0 ? Math.round(((job.sent_total + job.failed_total + job.skipped_total) / job.total_recipients) * 100) : 0;
    const showProgress = broadcastJobIsLive(job.status) || progress > 0;

    return `
        <div class="job-card" onclick="viewBroadcastDetails(${job.id})">
            <div class="job-card-row">
                <div class="job-card-main">
                    <div class="job-card-head">
                        <span class="job-card-icon">${contentIcon}</span>
                        <span class="job-card-title">${escapeHtml(job.name || 'Задача #' + job.id)}${deleteIcon}</span>
                        <span class="bc-badge bc-badge--${statusKey}">${escapeHtml(statusLabel)}</span>
                    </div>
                    ${job.source_label ? `<div class="job-card-source">${_bcIcon('users')}<span>${escapeHtml(job.source_label)}</span></div>` : ''}
                    <div class="job-card-meta">
                        <span class="bc-mi" title="Аккаунтов-отправителей в задаче">${_bcIcon('users')}<b>${job.total_accounts}</b> акк</span>
                        <span class="bc-mi" title="Всего получателей в базе">${_bcIcon('target')}<b>${job.total_recipients}</b> получателей</span>
                        <span class="bc-mi bc-mi--ok" title="Успешно отправлено">${_bcIcon('check')}<b>${job.sent_total}</b> отправлено</span>
                        <span class="bc-mi bc-mi--err" title="Ошибки отправки">${_bcIcon('cross')}<b>${job.failed_total}</b> ошибок</span>
                        <span class="bc-mi bc-mi--warn" title="Пропущено (лимиты/дубли)">${_bcIcon('skip')}<b>${job.skipped_total}</b> пропущено</span>
                        ${job.daily_limit > 0 ? `<span class="bc-mi" title="Отправлено сегодня / дневной лимит">${_bcIcon('chart')}<b>${job.sent_today || 0}/${job.daily_limit}</b> сегодня</span>` : ''}
                    </div>
                </div>
                <div class="job-card-actions" onclick="event.stopPropagation()">
                    ${buttons}
                </div>
            </div>
            ${showProgress ? `
                <div class="job-card-progress-row" title="Обработано ${job.sent_total + job.failed_total + job.skipped_total} из ${job.total_recipients}">
                    <div class="job-card-progress"><div style="width:${progress}%"></div></div>
                    <span class="job-card-progress-pct">${progress}%</span>
                </div>` : ''}
        </div>`;
}

async function startBroadcastJob(jobId) {
    try {
        const res = await fetch(`${API_URL}/broadcast/jobs/${jobId}/start`, {
            method: 'POST',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (!res.ok) { const err = await res.json(); throw new Error(err.detail || 'Ошибка'); }
        const data = await res.json().catch(() => ({}));
        if (data.waiting || data.status === 'waiting') {
            showNotification('⏳ Задача запущена — ждём загрузку аккаунтов…', 'success');
        } else {
            showNotification('✅ Рассылка запущена', 'success');
        }
        loadBroadcastJobs();
        if (currentBroadcastJobId === jobId) refreshBroadcastDetails(jobId);
    } catch (e) { showNotification(e.message, 'error'); }
}

async function pauseBroadcastJob(jobId) {
    try {
        const res = await fetch(`${API_URL}/broadcast/jobs/${jobId}/pause`, {
            method: 'POST',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (!res.ok) { const err = await res.json(); throw new Error(err.detail || 'Ошибка'); }
        showNotification('⏸ Рассылка приостановлена', 'success');
        loadBroadcastJobs();
        if (currentBroadcastJobId === jobId) refreshBroadcastDetails(jobId);
    } catch (e) { showNotification(e.message, 'error'); }
}

async function stopBroadcastJob(jobId, cancelWaiting) {
    const msg = cancelWaiting
        ? 'Отменить ожидание запуска? Задача вернётся в черновик.'
        : 'Остановить рассылку?';
    if (!confirm(msg)) return;
    try {
        const res = await fetch(`${API_URL}/broadcast/jobs/${jobId}/stop`, {
            method: 'POST',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (!res.ok) { const err = await res.json(); throw new Error(err.detail || 'Ошибка'); }
        showNotification('⏹ Рассылка остановлена', 'success');
        loadBroadcastJobs();
    } catch (e) { showNotification(e.message, 'error'); }
}

async function deleteBroadcastJob(jobId) {
    if (!confirm('Удалить задачу рассылки? Это действие необратимо.')) return;
    try {
        const res = await fetch(`${API_URL}/broadcast/jobs/${jobId}`, {
            method: 'DELETE',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (!res.ok) { const err = await res.json(); throw new Error(err.detail || 'Ошибка'); }
        showNotification('🗑 Задача удалена', 'success');
        loadBroadcastJobs();
    } catch (e) { showNotification(e.message, 'error'); }
}

async function resetBroadcastJob(jobId) {
    if (!confirm('⚠️ Сбросить ВСЕ данные?\n\nСчётчики, логи и статусы ВСЕХ получателей будут обнулены.\nРассылка начнётся заново по всем получателям!')) return;
    try {
        const res = await fetch(`${API_URL}/broadcast/jobs/${jobId}/reset`, {
            method: 'POST',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (!res.ok) { const err = await res.json(); throw new Error(err.detail || 'Ошибка'); }
        const _rd = await res.json().catch(() => ({})); showNotification(`🔄 ${_rd.message || 'Все данные сброшены'}`, 'success');
        refreshBroadcastDetails(jobId);
        loadBroadcastJobs();
    } catch (e) { showNotification(e.message, 'error'); }
}

async function resetBroadcastLimits(jobId) {
    if (!confirm('Сбросить дневные лимиты?\n\nСчётчики sent_today и failed_today обнулятся.\nУже отправленные получатели НЕ будут затронуты — повторной рассылки не будет.')) return;
    try {
        const res = await fetch(`${API_URL}/broadcast/jobs/${jobId}/reset-limits`, {
            method: 'POST',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (!res.ok) { const err = await res.json(); throw new Error(err.detail || 'Ошибка'); }
        const data = await res.json();
        showNotification(`🔄 ${data.message}`, 'success');
        refreshBroadcastDetails(jobId);
        loadBroadcastJobs();
    } catch (e) { showNotification(e.message, 'error'); }
}

async function retryBroadcastFailed(jobId) {
    if (!confirm('Вернуть failed-получателей в очередь?\n\nОни будут отправлены повторно.\nУже отправленные (sent) и пропущенные (skipped) НЕ затрагиваются.')) return;
    try {
        const res = await fetch(`${API_URL}/broadcast/jobs/${jobId}/retry-failed`, {
            method: 'POST',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        });
        if (!res.ok) { const err = await res.json(); throw new Error(err.detail || 'Ошибка'); }
        const data = await res.json();
        showNotification(`🔁 ${data.message}`, 'success');
        refreshBroadcastDetails(jobId);
        loadBroadcastJobs();
    } catch (e) { showNotification(e.message, 'error'); }
}

async function viewBroadcastDetails(jobId) {
    if (window.__ugModalsReady) await window.__ugModalsReady;
    const dm = document.getElementById('broadcastDetailsModal');
    if (!dm) { showNotification('Модалка не загрузилась — Ctrl+F5', 'error'); return; }
    currentBroadcastJobId = jobId;
    dm.classList.add('show');
    document.body.classList.add('ug-bc-modal-open');
    const hintEl0 = document.getElementById('bcDetailHint');
    if (hintEl0) { hintEl0.hidden = true; hintEl0.className = 'bcd-hint'; hintEl0.textContent = ''; }
    document.getElementById('bcDetailStats').innerHTML = '<div class="bcd-muted" style="grid-column:1/-1;">Загрузка...</div>';
    document.getElementById('bcDetailAccounts').innerHTML = '';
    document.getElementById('bcDetailLogs').innerHTML = '<div class="bcd-muted">Загрузка...</div>';
    await Promise.allSettled([refreshBroadcastDetails(jobId), refreshBroadcastLogs()]);

    if (broadcastDetailRefresh) clearInterval(broadcastDetailRefresh);
    broadcastDetailRefresh = setInterval(() => {
        refreshBroadcastDetails(jobId);
        refreshBroadcastLogs();
    }, 5000);
}

function closeBroadcastDetails() {
    document.getElementById('broadcastDetailsModal')?.classList.remove('show');
    if (!document.getElementById('createBroadcastModal')?.classList.contains('show')) {
        document.body.classList.remove('ug-bc-modal-open');
    }
    currentBroadcastJobId = null;
    if (broadcastDetailRefresh) { clearInterval(broadcastDetailRefresh); broadcastDetailRefresh = null; }
}

async function refreshBroadcastDetails(jobId) {
    const jid = jobId || currentBroadcastJobId;
    if (!jid) return;
    try {
        const res = await fetchWithTimeout(`${API_URL}/broadcast/jobs/${jid}`, {
            cache: 'no-store',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        }, 15000);
        if (!res.ok) {
            const errBody = await res.text().catch(() => '');
            throw new Error(errBody || `HTTP ${res.status}`);
        }
        window._bcDetailErrors = 0;
        const data = await res.json();
        const job = data.job || data;
        const qs = data.queue_stats || job.queue_stats || { sent: 0, failed: 0, skipped: 0, pending: 0 };
        const accounts = data.accounts || job.accounts || [];

        document.getElementById('bcDetailTitle').textContent = `📢 ${job.name || 'Задача #' + job.id}`;

        // Подсказка: реальная ошибка (last_error) → красным; нейтральный статус
        // (status_detail, напр. «Готово к отправке») → info, без пугающего ⚠.
        const hintEl = document.getElementById('bcDetailHint');
        if (hintEl) {
            const err = (job.last_error || '').trim();
            const detail = err || (job.status_detail || '').trim();
            if (!detail) {
                hintEl.hidden = true;
                hintEl.className = 'bcd-hint';
                hintEl.textContent = '';
            } else {
                hintEl.hidden = false;
                hintEl.className = 'bcd-hint ' + (err ? 'bcd-hint--err' : 'bcd-hint--info');
                hintEl.textContent = (err ? '⚠ ' : '') + detail;
            }
        }

        // Статистика
        document.getElementById('bcDetailStats').innerHTML = `
            <div class="bcd-stat bcd-stat--ok"><div class="bcd-stat-val">${qs.sent}</div><div class="bcd-stat-lbl">Отправлено</div></div>
            <div class="bcd-stat bcd-stat--err"><div class="bcd-stat-val">${qs.failed}</div><div class="bcd-stat-lbl">Ошибки</div></div>
            <div class="bcd-stat bcd-stat--warn"><div class="bcd-stat-val">${qs.skipped}</div><div class="bcd-stat-lbl">Пропущено</div></div>
            <div class="bcd-stat bcd-stat--queue"><div class="bcd-stat-val">${qs.pending}</div><div class="bcd-stat-lbl">В очереди</div></div>`;

        // Аккаунты
        document.getElementById('bcDetailAccounts').innerHTML = accounts.map(a => {
            const spam = (a.spam_status || '').toLowerCase();
            const resting = (a.status || '') === 'resting' || spam === 'limited';
            const statusColor = resting ? 'var(--warning)' : a.status === 'banned' ? 'var(--error)' : a.status === 'active' || a.status === 'pending' ? 'var(--text-secondary)' : 'var(--warning)';
            const badge = resting
                ? `<span style="color:var(--warning);font-size:0.68rem;">спамблок${a.spam_until ? ' до ' + escapeHtml(String(a.spam_until).slice(0, 16)) : ''}</span>`
                : '';
            const extraNote = (() => {
                const raw = String(a.last_error || '').trim();
                if (!raw) return '';
                const low = raw.toLowerCase();
                if (resting && (low.includes('спамблок') || low.includes('отлежк'))) return '';
                return raw;
            })();
            const sentLabel = a.send_cap ? `${a.sent_total}/${a.send_cap}` : `${a.sent_total}`;
            return `<div class="bcd-acc">
                <span>${escapeHtml(a.phone)} ${a.username ? '<span style="color:var(--text-secondary);">@' + escapeHtml(a.username) + '</span>' : ''}</span>
                <span style="display:flex;gap:8px;align-items:center;flex-shrink:0;">
                    ${badge}
                    <span style="color:var(--success);">✓${sentLabel}</span>
                    <span style="color:var(--error);">✗${a.failed_today}</span>
                    ${extraNote ? '<span style="color:' + statusColor + ';font-size:0.7rem;">' + escapeHtml(extraNote.substring(0, 30)) + '</span>' : ''}
                </span>
            </div>`;
        }).join('') || '<div class="bcd-muted" style="font-size:0.8rem;">Нет аккаунтов</div>';

        // Кнопки
        document.getElementById('bcDetailActions').innerHTML = broadcastJobDetailButtons(job, jid);

    } catch (e) {
        console.error('Broadcast details error:', e);
        const msg = e.name === 'AbortError' ? 'Таймаут — сервер не ответил за 15 сек' : (e.message || 'Ошибка загрузки');
        const st = document.getElementById('bcDetailStats');
        if (st) st.innerHTML = `<div class="bcd-muted" style="grid-column:1/-1;color:var(--error);">⚠️ ${escapeHtml(msg)}<br><button class="bcd-mini-btn" style="margin-top:8px;" onclick="refreshBroadcastDetails(${jid})">Повторить</button></div>`;
        const acc = document.getElementById('bcDetailAccounts');
        if (acc) acc.innerHTML = '';
    }
}

async function refreshBroadcastLogs() {
    if (!currentBroadcastJobId) return;
    const container = document.getElementById('bcDetailLogs');
    try {
        const res = await fetchWithTimeout(`${API_URL}/broadcast/jobs/${currentBroadcastJobId}/logs?limit=100`, {
            cache: 'no-store',
            headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
        }, 15000);
        if (!res.ok) {
            throw new Error(`HTTP ${res.status}`);
        }
        const data = await res.json();
        if (!data.logs || !data.logs.length) {
            container.innerHTML = '<div style="color:var(--text-secondary);text-align:center;padding:10px;">Нет логов</div>';
            return;
        }
        const levelColors = { 'info': 'var(--text-primary)', 'warn': '#f59e0b', 'warning': '#f59e0b', 'error': '#fc8181' };
        const today = new Date().toLocaleDateString();
        container.innerHTML = data.logs.map(l => {
            let timeStr = '';
            if (l.created_at) {
                // Убедимся что JS парсит как UTC (isoformat Python даёт +00:00)
                const raw = l.created_at.includes('Z') || l.created_at.includes('+') || l.created_at.includes('-', 10)
                    ? l.created_at : l.created_at + 'Z';
                const d = new Date(raw);
                const logDay = d.toLocaleDateString();
                const hms = d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
                timeStr = logDay !== today ? `${d.toLocaleDateString([], { month: '2-digit', day: '2-digit' })} ${hms}` : hms;
            }
            const color = levelColors[l.level] || 'var(--text-primary)';
            const repeat = (l.repeat_count && l.repeat_count > 1)
                ? ` <span style="color:var(--text-secondary);font-size:0.7rem;">×${l.repeat_count}</span>`
                : '';
            return `<div style="color:${color};margin-bottom:2px;line-height:1.4;"><span style="color:var(--text-secondary);font-size:0.7rem;">[${timeStr}]</span> ${escapeHtml(l.message)}${repeat}</div>`;
        }).join('');
    } catch (e) {
        console.error('Logs error:', e);
        const msg = e.name === 'AbortError' ? 'Таймаут загрузки логов' : 'Не удалось загрузить логи';
        container.innerHTML = `<div style="color:var(--warning);text-align:center;padding:10px;">⚠️ ${escapeHtml(msg)} <button onclick="refreshBroadcastLogs()" style="margin-left:6px;cursor:pointer;">↻</button></div>`;
    }
}

// ============================================================================
// MONITORING v2 Functions — Task-based multi-source
// ============================================================================
const _ugExports = {
  loadBroadcastJobs, createBroadcastJob, saveBroadcastJob, editBroadcastJob,
  loadBroadcastAccounts, startBroadcastJob, pauseBroadcastJob, stopBroadcastJob,
  deleteBroadcastJob, resetBroadcastJob, resetBroadcastLimits, retryBroadcastFailed,
  viewBroadcastDetails, closeBroadcastDetails, refreshBroadcastDetails, refreshBroadcastLogs,
  showCreateBroadcastModal, closeBroadcastModal, setBroadcastContentType, setBroadcastSource,
  autoSpintax, aiGenerateSpintax, applyBcAiVariant, onBcAiProfileChange,
  deleteBcAiVariant, addCurrentTextAsVariant,
  saveBcAiCustomProfile, deleteBcAiCustomProfile, loadBcAiModels, syncBcAiFromSettings,
  loadBcAiVariantCount, saveBcAiVariantCount,
  regenSpintax, clearSpintax, previewSpintaxResult, addPostLinkRow,
  loadBroadcastParserBases, handleBroadcastFileUpload, handleBroadcastPhonesFileUpload,
  setBcPhonesMode, updateBcPhonesManualCount, getPostLinksData, parsePhoneLines,
  parseManualUsers, renderBroadcastJobCard, requeueProcessedRecipientsForEditJob,
  clearRecipientsForEditJob, loadContactsBase, saveCurrentAsBase, checkPhoneTarget,
};
Object.assign(window, _ugExports);

if (window.__ugModalsReady) {
    window.__ugModalsReady.then(() => {
        loadBcAiModels();
        loadBcAiVariantCount();
        document.getElementById('bcAiVariantCount')?.addEventListener('change', saveBcAiVariantCount);
    }).catch(() => {});
} else {
    document.addEventListener('DOMContentLoaded', () => {
        loadBcAiModels();
        loadBcAiVariantCount();
        document.getElementById('bcAiVariantCount')?.addEventListener('change', saveBcAiVariantCount);
    });
}

function adjustBroadcastListRefresh(jobs) {
    const fast = (jobs || []).some(j => broadcastJobIsLive(j.status));
    if (_broadcastListRefresh) clearInterval(_broadcastListRefresh);
    _broadcastListRefresh = setInterval(() => {
        const tab = document.getElementById('tab-broadcast');
        if (!tab || !tab.classList.contains('active')) return;
        loadBroadcastJobs();
    }, fast ? 3000 : 12000);
}

function scheduleBroadcastListRefresh() {
  adjustBroadcastListRefresh([]);
}
scheduleBroadcastListRefresh();
