<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ tp_app_label }} — Логи</title>
    <link rel="icon" href="/static/favicon.ico" type="image/x-icon">
    <link rel="stylesheet" href="/static/bootstrap-icons.min.css">
    <link rel="stylesheet" href="/static/style.css">
    <style>
        :root {
            --lw-bg: #07070d;
            --lw-bg-elev: rgba(20, 20, 32, 0.55);
            --lw-border: rgba(139, 92, 246, 0.15);
            --lw-border-strong: rgba(139, 92, 246, 0.32);
            --lw-text: #e2e8f0;
            --lw-text-dim: #94a3b8;
            --lw-text-muted: #64748b;
            --lw-accent: #8b5cf6;
            --lw-accent-soft: rgba(139,92,246,0.10);
        }
        * { box-sizing: border-box; }
        body {
            margin: 0;
            background: #050509;
            color: var(--lw-text);
            /* Моноширный шрифт — терминал-стиль консоли. */
            font-family: "JetBrains Mono", "Cascadia Mono", Consolas, "Courier New", monospace;
            height: 100vh; overflow: hidden;
        }
        .lw-shell { display: flex; flex-direction: column; height: 100vh; }

        /* ── Header ── */
        .lw-header {
            display: flex; align-items: center; gap: 14px;
            padding: 14px 20px;
            background: rgba(12, 12, 20, 0.65);
            border-bottom: 1px solid var(--lw-border);
            
            
            position: relative; z-index: 2;
        }
        .lw-title {
            display: flex; align-items: center; gap: 10px;
            font-size: 0.95rem; font-weight: 700; color: var(--lw-text);
            letter-spacing: 0.2px;
        }
        .lw-title-icon {
            display: inline-flex; align-items: center; justify-content: center;
            width: 28px; height: 28px;
            background: linear-gradient(135deg, rgba(139, 92, 246, 0.35), rgba(99, 102, 241, 0.18));
            border: 1px solid rgba(139, 92, 246, 0.4);
            border-radius:5px;
            color: #c4b5fd; font-size: 0.85rem;
            
        }
        .lw-counter {
            font-size: 0.68rem; font-weight: 600; color: #c4b5fd;
            background: rgba(139, 92, 246, 0.12);
            border: 1px solid rgba(139, 92, 246, 0.22);
            padding: 3px 9px; border-radius:5px;
            font-variant-numeric: tabular-nums;
        }
        .lw-pulse {
            display: inline-block; width: 6px; height: 6px;
            background: #64748b; border-radius: 50%;
            margin-right: 5px;
            transition: background 0.3s, box-shadow 0.3s;
        }
        .lw-pulse.active {
            background: #22c55e;
            
            animation: lw-pulse 1.6s ease-in-out infinite;
        }
        @keyframes lw-pulse {
            0%, 100% { opacity: 1; transform: scale(1); }
            50% { opacity: 0.4; transform: scale(0.85); }
        }

        /* ── Tabs ── */
        .lw-tabs {
            display: flex; gap: 5px; flex-wrap: wrap; align-items: center;
            padding: 10px 16px;
            border-bottom: 1px solid rgba(255, 255, 255, 0.03);
            background: rgba(10, 10, 18, 0.5);
            
            
        }
        .lw-tab {
            display: inline-flex; align-items: center; gap: 5px;
            padding: 4px 9px; font-size: 0.7rem; font-weight: 500;
            color: var(--lw-text-dim); background: rgba(255, 255, 255, 0.02);
            border: 1px solid rgba(255, 255, 255, 0.05); border-radius:4px;
            cursor: pointer; transition: all 0.18s ease;
            font-family: inherit;
            white-space: nowrap;
        }
        .lw-tab i { font-size: 0.78rem; }
        .lw-tab:hover {
            background: rgba(139, 92, 246, 0.08);
            color: var(--lw-text);
            border-color: rgba(139, 92, 246, 0.2);
        }
        .lw-tab.active {
            background: linear-gradient(135deg, rgba(139, 92, 246, 0.22), rgba(99, 102, 241, 0.14));
            border-color: var(--lw-border-strong);
            color: #ddd6fe;
            
        }
        .lw-badge {
            display: inline-flex; align-items: center; justify-content: center;
            min-width: 16px; height: 15px; padding: 0 5px;
            background: rgba(255, 255, 255, 0.06); color: var(--lw-text-muted);
            font-size: 0.6rem; font-weight: 700;
            border-radius:5px;
            font-variant-numeric: tabular-nums;
        }
        .lw-badge:empty { display: none; }
        .lw-badge.has-warn { background: rgba(245, 158, 11, 0.22); color: #fcd34d; }
        .lw-badge.has-err  { background: rgba(239, 68, 68, 0.25); color: #fca5a5; }
        .lw-tab.active .lw-badge { background: rgba(139, 92, 246, 0.32); color: #ede9fe; }

        /* ── Actions ── */
        .lw-actions {
            margin-left: auto; display: flex; gap: 4px;
            flex-shrink: 0;  /* не переносится на новый ряд при wrap табов */
        }
        .lw-icon-btn {
            display: inline-flex; align-items: center; gap: 5px;
            background: rgba(255, 255, 255, 0.03);
            border: 1px solid rgba(255, 255, 255, 0.06);
            color: var(--lw-text-dim); border-radius:4px;
            padding: 4px 9px; font-size: 0.7rem; font-weight: 500;
            cursor: pointer; transition: all 0.15s ease;
            font-family: inherit;
            white-space: nowrap;
        }
        .lw-icon-btn:hover {
            background: rgba(139, 92, 246, 0.14);
            color: #c4b5fd;
            border-color: rgba(139, 92, 246, 0.28);
        }
        .lw-icon-btn.active {
            background: rgba(34, 197, 94, 0.14);
            color: #86efac;
            border-color: rgba(34, 197, 94, 0.32);
            
        }
        .lw-icon-btn.active:hover {
            background: rgba(34, 197, 94, 0.2);
            color: #bbf7d0;
        }
        .lw-icon-btn i { font-size: 0.78rem; }

        /* ── Stream ── */
        .lw-stream {
            flex: 1; overflow-y: auto;
            padding: 14px 18px 32px;
            font-family: "JetBrains Mono", "SF Mono", "Cascadia Code", "Consolas", ui-monospace, monospace;
            font-size: 0.78rem; line-height: 1.6;
            color: var(--lw-text);
            font-feature-settings: "calt" 0;
        }
        .lw-stream::-webkit-scrollbar { width: 10px; }
        .lw-stream::-webkit-scrollbar-track { background: transparent; }
        .lw-stream::-webkit-scrollbar-thumb {
            background: rgba(139, 92, 246, 0.22);
            border-radius:4px;
            border: 2px solid #050509;
        }
        .lw-stream::-webkit-scrollbar-thumb:hover { background: rgba(139, 92, 246, 0.42); }

        /* компактная плотность строк */
        .lw-row {
            display: flex; gap: 6px; padding: 0 8px;
            font-size: 0.68rem; line-height: 1.30;
            border-radius: 0; margin-bottom: 0;
            position: relative;
        }
        .lw-row:hover { background: rgba(255, 255, 255, 0.04); }
        /* яркие контрастные цвета уровней, не размытые */
        .lw-row.lvl-INFO    { color: #e2e8f0; }
        .lw-row.lvl-WARNING { color: #fbbf24; font-weight: 500; }
        .lw-row.lvl-ERROR   { color: #f87171; font-weight: 500; }
        .lw-row.lvl-SUCCESS { color: #4ade80; font-weight: 500; }
        .lw-row.lvl-DEBUG   { color: #64748b; }
        /* Состояние аккаунта важнее уровня записи: строка про мёртвый акк должна
           быть красной, даже если сама запись обычная. Обычные действия остаются
           белыми — цветом выделяем только то, с чем клиенту надо что-то делать. */
        /* Провал прогона — светлее «мёртвого»: там переавторизация, тут проверка связи. */
        .lw-row.st-fail     { color: #fca5a5; font-weight: 500; }
        .lw-row.st-dead     { color: #f87171; font-weight: 500; }
        .lw-row.st-alive    { color: #4ade80; font-weight: 500; }
        .lw-row.st-spam     { color: #fbbf24; font-weight: 500; }
        .lw-row.st-eternal  { color: #e0705c; font-weight: 600; }
        /* Заморозка — оливковый: она между «жив» и «мёртв», и путать её ни с
           зелёным, ни с голубым подключением нельзя. Прежняя бирюза стояла
           вплотную к цвету строки подключения. */
        .lw-row.st-frozen   { color: #b5bd3c; font-weight: 500; }
        /* Строка подключения цвета не имеет — она белая, как обычное сообщение.
           Владелец 10.08.2026: «подключение к прокси — белый цвет». Это не
           вердикт и делать с ней нечего, а цветом в окне выделено только то,
           что требует внимания. */
        .lw-time {
            color: var(--lw-text-muted); flex-shrink: 0;
            font-variant-numeric: tabular-nums;
            min-width: 56px;
        }
        .lw-mod {
            flex-shrink: 0;
            /* Ширина под самую длинную подпись: иначе текст сообщений съезжает
               от строки к строке и лог перестаёт читаться колонкой. */
            min-width: 12ch; text-align: left;
            opacity: 0.75;
        }
        /* Поток и номер — свои колонки фиксированной ширины. Пока они ехали
           внутри текста, вердикты стояли на разной высоте строки и глазом было
           не поймать, где какой аккаунт. */
        .lw-slot {
            flex-shrink: 0; width: 11ch;
            font-variant-numeric: tabular-nums;
            opacity: 0.55;
        }
        .lw-num {
            flex-shrink: 0; width: 17ch;
            font-variant-numeric: tabular-nums;
            opacity: 0.85;
        }
        /* У строки без аккаунта эти колонки пустые, и 28 символов пустоты
           отрывали текст от времени — на экране зияла дыра во всю ширину.
           Схлопываем их только у таких строк: вердикты аккаунтов между собой
           по-прежнему стоят ровным столбиком, ради которого колонки и заведены. */
        .lw-row.lw-plain .lw-slot,
        .lw-row.lw-plain .lw-num { width: 0; }
        .lw-icon { display: none; }
        /* перенос длинных сообщений с отступом под текст msg, не под [time] */
        .lw-msg {
            word-break: break-word; flex: 1;
            padding-left: 0;
            text-indent: 0;
        }

        /* ── Empty state ── */
        .lw-empty {
            text-align: center; padding: 80px 24px;
            color: var(--lw-text-muted);
            font-size: 0.85rem;
            font-family: -apple-system, "Inter", "Segoe UI", sans-serif;
        }
        .lw-empty-icon {
            display: inline-flex; align-items: center; justify-content: center;
            width: 56px; height: 56px;
            background: rgba(139, 92, 246, 0.06);
            border: 1px solid rgba(139, 92, 246, 0.14);
            border-radius:7px;
            color: #a78bfa; font-size: 1.7rem;
            margin-bottom: 18px;
        }
        .lw-empty-title {
            font-weight: 600; color: var(--lw-text); font-size: 0.95rem;
            margin-bottom: 6px;
        }
        .lw-empty-sub { font-size: 0.78rem; color: var(--lw-text-muted); }

        /* подсветка phone-номеров в логах */
        .lw-phone {
            cursor: pointer;
            border-bottom: 1px dashed rgba(168, 139, 250, 0.4);
            transition: color 0.1s, background 0.1s;
        }
        .lw-phone:hover { color: #c4b5fd; background: rgba(139, 92, 246, 0.12); }
        .lw-row--highlighted {
            background: rgba(251, 191, 36, 0.10) !important;
            border-left: 2px solid #fbbf24;
            padding-left: 4px;
        }
        .lw-row--highlighted .lw-phone {
            background: rgba(251, 191, 36, 0.25);
            border-bottom-color: #fbbf24;
            color: #fbbf24;
            font-weight: 600;
        }
    </style>
</head>
<body>
<div class="lw-shell">
    <!-- В модалке своя шапка с названием и крестиком, поэтому эту прячем: иначе
         «Логи работы» дублируется двумя строками подряд. embed=1 ставит iframe. -->
    <div class="lw-header" id="lw-header">
        <span class="lw-title">
            <span class="lw-title-icon"><i class="bi bi-terminal-fill"></i></span>
            Логи работы
        </span>
        <span class="lw-counter" id="lw-counter" title="Сколько аккаунтов сейчас занято задачами"><span class="lw-pulse" id="lw-pulse"></span><span id="lw-counter-text">простой</span></span>
    </div>
    <div class="lw-tabs">
        <!-- табы-фильтры по модулям убраны: модуль виден на строке через цветной badge.
             Скрытый дефолт-фильтр data-mod="all" сохранён для совместимости с lwSetFilter(). -->
        <button class="lw-tab active" data-mod="all" style="display:none;"></button>
        <div class="lw-actions" style="margin-left:0;">
            <button class="lw-icon-btn lw-action-labeled" onclick="lwClear()" title="Очистить все записи"><i class="bi bi-trash"></i><span>Очистить</span></button>
            <button class="lw-icon-btn lw-action-labeled active" onclick="lwToggleAutoscroll(this)" title="Авто-скролл вниз при новых записях" id="lw-autoscroll-btn"><i class="bi bi-arrow-down-circle-fill"></i><span>Авто-скролл</span></button>
        </div>
    </div>
    <div class="lw-stream" id="lw-stream">
        <div class="lw-empty" id="lw-empty">
            <div class="lw-empty-icon"><i class="bi bi-hourglass-split"></i></div>
            <div class="lw-empty-title">Жду событий</div>
            <div class="lw-empty-sub">Запусти задачу — здесь будет видно что происходит в реальном времени</div>
        </div>
    </div>
</div>

<script>
var _lwFilter = 'all';
var _lwAutoScroll = true;
var _lwLastId = 0;

// Подписи задач читаются без словаря: «РАС» одинаково подходило и рассылке по
// чатам, и рассылке по ЛС, а по строке лога понять, какая из двух работает, было
// нельзя. Живые чаты писались как «АО», хотя автоответчик к ним не относится —
// он только отвечает в личку, а это пассивный сбор.
var _MOD_LABELS = {spam:'ЧАТЫ', dm:'ЛС', join:'ПОДПИСКА', checker:'ПРОВЕРКА', invite:'ИНВАЙТ', screen:'ЭКРАН', parser:'ПАРСЕР', monitor:'МОНИТОР',
                   warmup:'ПРОГРЕВ', autoreply:'АВТООТВЕТ', livechats:'ЖИВЫЕ ЧАТЫ', system:'СИСТЕМА'};
var _MOD_COLORS = {spam:'#a78bfa', dm:'#22c55e', join:'#3b82f6', checker:'#f59e0b', invite:'#818cf8', screen:'#94a3b8', parser:'#ec4899', monitor:'#f97316',
                   warmup:'#eab308', autoreply:'#06b6d4', livechats:'#14b8a6', system:'#64748b'};
var _LVL_ICONS = {
    INFO:    'bi-info-circle-fill',
    WARNING: 'bi-exclamation-triangle-fill',
    ERROR:   'bi-x-circle-fill',
    SUCCESS: 'bi-check-circle-fill',
    DEBUG:   'bi-dot',
};

// Цвет строки по СОСТОЯНИЮ аккаунта, а не по уровню записи. Клиент читает лог
// глазами и должен с одного взгляда видеть, где проблема и какая именно.
// Порядок проверок важен: «вечный спам» ловим раньше обычного спам-блока,
// заморозку — раньше всего остального, иначе она уедет в другой цвет.
// Провал ловим ДО «живого»: строка «ошибка — не подключён» содержит «подключён»
// и без этой проверки красилась зелёным, как удачное подключение. Клиент видел
// сплошной зелёный список там, где не прошло ни одно вступление.
// Пропущенный АККАУНТ красный: он остался непроверенным и к нему надо
// вернуться. Ловим по «повторите позже» — эта фраза стоит только в строках про
// аккаунт. По одному слову «пропущен» ловить нельзя: оно есть и у ЦЕЛЕЙ («канал
// пропущен — включено только чаты/группы»), а там клиент видит собственную
// настройку, а не сбой, и красный там — ложная тревога. Основной цвет этой
// строке всё равно даёт состояние записи (tp_state=fail), текст — страховка.
var _LW_FAIL = /ошибка|сбой|провал|не\s+удалось|не\s+подключ|не\s+смог|не\s+получилось|повторите позже|timeout|таймаут/;

function _lwStateClass(text) {
    var t = (text || '').toLowerCase();
    // «Без спама» — раньше всего: строка содержит слово «спам» и по общему
    // правилу уходила в жёлтый, то есть чистый аккаунт читался как
    // заблокированный. Ровно этот случай владелец и назвал: «без спама —
    // зелёный».
    // Ловим ВЕРДИКТ одного аккаунта, а не итоговую сводку прогона: в сводке
    // «Проверка завершена (50/50): без спама 0, спам 0, заморожено 20, мёртвых 30»
    // те же слова стоят рядом с цифрой, и по широкому правилу прогон, где не
    // выжил никто, светился бы зелёным «всё чисто».
    if (/без\s+спам-?блока|без\s+спама\s*[(,.]|без\s+спама$|без ограничений|ограничений нет|свободен/.test(t)) return 'st-alive';
    if (/заморож|заморозк|frozen/.test(t)) return 'st-frozen';
    if (/вечн(ый|ого|ым) спам|навсегда|постоянн(о|ый) ограничен|permanent/.test(t)) return 'st-eternal';
    if (/спам-?блок|спамблок|временно ограничен|ограничен до|просит подождать|peerflood|floodwait/.test(t)) return 'st-spam';
    if (/мёртв|мертв|сессия слетела|сессия отозвана|переавторизуй|разлогин/.test(t)) return 'st-dead';
    if (_LW_FAIL.test(t)) return 'st-fail';
    if (/живой|без ограничений|свободен|ограничений нет|подключ(ил|ён|ен)/.test(t)) return 'st-alive';
    return '';
}

// Состояние, присланное самим модулем, всегда важнее догадки по тексту.
var _LW_STATES = {alive:1, spam:1, eternal:1, dead:1, frozen:1, fail:1};
// Голова строки результата: [Поток N] [номер]. Её выносим в отдельные колонки,
// чтобы вердикты стояли друг под другом и читались столбиком.
var _LW_HEAD = /^\[Поток (\d+)\]\s*\[([^\]]{1,22})\]\s*/;

function lwAdd(msg, level, mod, ts, state) {
    mod = mod || 'system';
    level = level || 'INFO';
    var el = document.getElementById('lw-stream');
    if (!el) return;
    // Убираем placeholder на первой записи
    var ph = document.getElementById('lw-empty');
    if (ph) ph.remove();

    var row = document.createElement('div');
    row.className = 'lw-row lvl-' + level;
    row.setAttribute('data-mod', mod);
    if (_lwFilter !== 'all' && _lwFilter !== mod) row.style.display = 'none';
    var clean = (msg || '')
        .replace(/[☀-➿]/g, '')
        .replace(/[\uD83C-\uDBFF][\uDC00-\uDFFF]/g, '')
        .replace(/[︎️]/g, '')
        .replace(/\s{2,}/g, ' ')
        .trim();
    // реальное время события из record.ts (UNIX seconds), а не момент показа в UI
    var d = (ts && ts > 0) ? new Date(ts * 1000) : new Date();
    var time = d.toLocaleTimeString('ru-RU', {hour: '2-digit', minute: '2-digit', second: '2-digit'});
    var modColor = _MOD_COLORS[mod] || '#64748b';
    // data-phones для быстрого фильтра по клику без regex
    var phonesInRow = (clean.match(/\+?\d{10,15}/g) || []).map(function(p){return p.replace(/\D/g,'');});
    if (phonesInRow.length) row.setAttribute('data-phones', '|' + phonesInRow.join('|') + '|');
    // Голову строки — в свои колонки, хвост остаётся вердиктом.
    var head = _LW_HEAD.exec(clean);
    var slotTxt = head ? ('Поток ' + head[1]) : '';
    var numTxt = head ? head[2] : '';
    var body = head ? clean.slice(head[0].length) : clean;
    // подсвечиваем телефонные номера (10-15 цифр) — клик выделит все строки про этот акк
    var msgHtml = escapeHtmlLw(body).replace(/\+?\d{10,15}/g, function(m) {
        var digits = m.replace(/\D/g, '');
        return '<span class="lw-phone" data-phone="' + digits + '" onclick="lwTogglePhone(\'' + digits + '\',event)">' + m + '</span>';
    });
    var numHtml = '';
    if (numTxt) {
        var numDigits = numTxt.replace(/\D/g, '');
        // Кликабельным делаем только настоящий номер (тот же диапазон, что и у
        // фильтра). Иначе подчёркнутый «?» звал бы кликнуть, а клик снимал бы
        // подсветку и не находил ни одной строки.
        if (numDigits.length >= 10 && numDigits.length <= 15) {
            numHtml = '[<span class="lw-phone" data-phone="' + numDigits +
                      '" onclick="lwTogglePhone(\'' + numDigits + '\',event)">' +
                      escapeHtmlLw(numTxt) + '</span>]';
        } else {
            numHtml = '[' + escapeHtmlLw(numTxt) + ']';
        }
    }
    // Состояние аккаунта перекрывает цвет уровня. Ошибка остаётся красной в
    // любом случае: класс уровня ERROR и класс «мёртвый» дают один цвет.
    // Присланное модулем состояние важнее разбора текста: «Без спам-блока»
    // содержит «спам-блок» и по словам покрасилось бы жёлтым.
    var _st = (state && _LW_STATES[state]) ? ('st-' + state) : _lwStateClass(clean);
    if (_st && level !== 'ERROR') row.className = 'lw-row lvl-' + level + ' ' + _st;
    // Строка не про аккаунт — колонки «Поток» и «номер» ей не нужны, пусть
    // текст начинается сразу после времени.
    if (!head) row.classList.add('lw-plain');
    // Формат строки: [МОД] [время] [Поток N] [номер] вердикт — колонки пустуют у
    // обычных сообщений, зато результаты аккаунтов стоят ровным столбиком.
    row.innerHTML =
        '<span class="lw-mod" style="color:' + modColor + ';">[' + (_MOD_LABELS[mod] || mod.toUpperCase()) + ']</span>' +
        '<span class="lw-time">[' + time + ']</span>' +
        '<span class="lw-slot">' + (slotTxt ? '[' + slotTxt + ']' : '') + '</span>' +
        '<span class="lw-num">' + numHtml + '</span>' +
        '<span class="lw-msg">' + msgHtml + '</span>';
    el.appendChild(row);
    // Если уже есть выбранный phone — подсвечиваем новую строку если она про него
    if (window._lwHighlightedPhone && phonesInRow.indexOf(window._lwHighlightedPhone) !== -1) {
        row.classList.add('lw-row--highlighted');
    }
    if (_lwAutoScroll) el.scrollTop = el.scrollHeight;
    // Не разрастаемся: храним последние 2000 строк
    while (el.children.length > 2000) el.removeChild(el.firstChild);
    // Badge на табе для не-активного фильтра
    if (mod !== _lwFilter && _lwFilter !== 'all') {
        var tab = document.querySelector('.lw-tab[data-mod="' + mod + '"]');
        if (tab) {
            var b = tab.querySelector('.lw-badge');
            if (b) {
                var n = parseInt(b.textContent || '0') + 1;
                b.textContent = n > 99 ? '99+' : n;
                if (level === 'ERROR') b.classList.add('has-err');
                else if (level === 'WARNING' && !b.classList.contains('has-err')) b.classList.add('has-warn');
            }
        }
    }
}

function escapeHtmlLw(s) {
    return String(s).replace(/[&<>"]/g, function(c) {
        return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];
    });
}

// подсветка всех логов с одинаковым phone-номером; повторный клик / клик в пустое — снимает
function lwTogglePhone(phone, ev) {
    if (ev) { ev.stopPropagation(); ev.preventDefault(); }
    var el = document.getElementById('lw-stream');
    if (!el) return;
    var current = window._lwHighlightedPhone || '';
    // Снимаем предыдущую подсветку
    Array.from(el.querySelectorAll('.lw-row--highlighted')).forEach(function(r) {
        r.classList.remove('lw-row--highlighted');
    });
    if (current === phone) {
        window._lwHighlightedPhone = '';
        return;
    }
    window._lwHighlightedPhone = phone;
    var marker = '|' + phone + '|';
    Array.from(el.children).forEach(function(r) {
        var dp = r.getAttribute && r.getAttribute('data-phones');
        if (dp && dp.indexOf(marker) !== -1) r.classList.add('lw-row--highlighted');
    });
}

// Снимаем подсветку при клике в пустое место стрима
document.addEventListener('click', function(e) {
    if (e.target && (e.target.classList.contains('lw-phone') || e.target.closest('.lw-phone'))) return;
    if (window._lwHighlightedPhone) lwTogglePhone(window._lwHighlightedPhone);
});

function lwSetFilter(btn, mod) {
    _lwFilter = mod;
    document.querySelectorAll('.lw-tab').forEach(function(t) { t.classList.remove('active'); });
    if (btn) btn.classList.add('active');
    var el = document.getElementById('lw-stream');
    Array.from(el.children).forEach(function(row) {
        var rm = row.getAttribute('data-mod') || 'system';
        row.style.display = (mod === 'all' || rm === mod) ? '' : 'none';
    });
    el.scrollTop = el.scrollHeight;
    if (btn) {
        var b = btn.querySelector('.lw-badge');
        if (b) { b.textContent = ''; b.classList.remove('has-err', 'has-warn'); }
    }
}

function lwClear() {
    var el = document.getElementById('lw-stream');
    el.innerHTML = '<div class="lw-empty" id="lw-empty">' +
        '<div class="lw-empty-icon"><i class="bi bi-check2-circle"></i></div>' +
        '<div class="lw-empty-title">Логи очищены</div>' +
        '<div class="lw-empty-sub">Новые события появятся здесь сразу как произойдут</div>' +
        '</div>';
    document.querySelectorAll('.lw-tab .lw-badge').forEach(function(b) {
        b.textContent = '';
        b.classList.remove('has-err', 'has-warn');
    });
}

function lwToggleAutoscroll(btn) {
    _lwAutoScroll = !_lwAutoScroll;
    btn.classList.toggle('active', _lwAutoScroll);
    var icon = btn.querySelector('i');
    if (icon) icon.className = _lwAutoScroll ? 'bi bi-arrow-down-square-fill' : 'bi bi-arrow-down-square';
    btn.title = _lwAutoScroll ? 'Авто-скролл: вкл' : 'Авто-скролл: выкл';
}

var _lwBootToken = null;
function lwPoll() {
    fetch('/api/logs/recent?since=' + _lwLastId, { credentials: 'same-origin' })
        .then(function(r) { return r.json(); })
        .then(function(d) {
            // детект рестарта сервера: при смене boot-token сбрасываем _lwLastId и DOM,
            // иначе новые логи (id < старого since) отфильтруются и стрим будет пустым
            if (d && d.boot) {
                if (_lwBootToken === null) {
                    _lwBootToken = d.boot;
                } else if (_lwBootToken !== d.boot) {
                    _lwBootToken = d.boot;
                    _lwLastId = 0;
                    var _logEl = document.getElementById('lw-stream');
                    if (_logEl) _logEl.innerHTML = '';
                }
            }
            if (d && d.logs) {
                d.logs.forEach(function(l) {
                    if (l.id > _lwLastId) _lwLastId = l.id;
                    lwAdd(l.message, l.level, l.module || 'system', l.ts, l.state || '');
                });
            }
            // Пульсирующая точка — только когда есть активная задача
            var pulse = document.getElementById('lw-pulse');
            if (pulse) pulse.classList.toggle('active', !!(d && d.active));
            // Counter: «N акков работают» + детализация модулей в tooltip
            var ctext = document.getElementById('lw-counter-text');
            var ctitle = document.getElementById('lw-counter');
            if (ctext) {
                var busy = (d && d.busy_accounts) || 0;
                if (busy === 0) {
                    ctext.textContent = 'простой';
                } else {
                    var noun = busy === 1 ? 'акк работает' : (busy < 5 ? 'акка работают' : 'акков работают');
                    // real-time прогресс: N акков, M из K отправлено
                    var sent = (d && d.rt_sent) || 0;
                    var total = (d && d.rt_total) || 0;
                    // глагол приходит с бэка (проверено/отправлено)
                    var verb = (d && d.rt_verb) || 'отправлено';
                    var statSuffix = '';
                    if (total > 0) {
                        statSuffix = ' · ' + sent + ' из ' + total + ' ' + verb;
                    } else if (sent > 0) {
                        statSuffix = ' · ' + sent + ' ' + verb;
                    }
                    ctext.textContent = busy + ' ' + noun + statSuffix;
                }
            }
            if (ctitle && d && d.active_modules) {
                var details = d.active_modules.map(function(m) { return m[0]; }).join(', ');
                ctitle.title = details ? 'Работают: ' + details : 'Сколько аккаунтов сейчас занято задачами';
            }
        })
        .catch(function() {});
}
setInterval(lwPoll, 1500);
lwPoll();
</script>
<script>
// Встроенный режим (?embed=1): страницу открыли внутри модалки, у которой уже
// есть своя шапка с названием и крестиком. Прячем собственную, чтобы «Логи
// работы» не шло двумя одинаковыми строками подряд. Счётчик занятости при этом
// не теряем — переносим его в панель инструментов.
(function () {
    try {
        if (new URLSearchParams(location.search).get('embed') !== '1') return;
        var h = document.getElementById('lw-header');
        var c = document.getElementById('lw-counter');
        var bar = document.querySelector('.lw-toolbar') || document.querySelector('.lw-actions');
        if (c && bar) { c.style.marginLeft = 'auto'; bar.appendChild(c); }
        if (h) h.style.display = 'none';
    } catch (_) {}
})();
</script>
</body>
</html>
