<?php
/**
 * Кабинет VK-лидгена: настройки промптов, расписание, запуск и остановка модулей.
 * Подключается из index.php при ?t=settings (сессия уже проверена).
 */
declare(strict_types=1);

require_once __DIR__ . '/sched_calc.php';   // расчёт «следующий запуск по расписанию»

// лимит ВК: если сегодня ВК ограничил действия — показываем предупреждение и не запускаемся
$vkLimited = false;
$vkLimitReason = '';
try {
    $vkLimited = (string)s($pdo, 'vk_limit_date', '') === date('Y-m-d', time() + 3 * 3600);
    $vkLimitReason = (string)s($pdo, 'vk_limit_reason', '');
} catch (Throwable $e) { $vkLimited = false; }

$MODULES = [
    'friends_check' => ['Проверка друзей', 'кто добавил в друзья → в базу или в исключения'],
    'comments'      => ['Комментарии', 'комментирует посты, ставит лайк, отправляет заявку'],
    'messages'      => ['Сообщения', 'пишет в ЛС тем, кто принял заявку'],
    'friend_followup' => ['Добор заявок', 'досылает заявки в друзья тем, кого прокомментировали (лимит ВК — 50/сутки)'],
    'replies'       => ['Ответы на входящие', 'отвечает тем, кто написал в личку первым (агрессия — не отвечаем)'],
    'cleanup'       => ['Чистка заявок', 'отменяет зависшие исходящие заявки'],
];

// сколько контактов ждут добора заявки (для колонки «Ждут обработки»)
$followupQueue = 0;
try {
    $ph = str_repeat('?,', 7);
    $st = $pdo->prepare("SELECT COUNT(*) FROM contacts c
        WHERE COALESCE(c.friend_status,'') NOT IN
              ('Добавился','Отправлено сообщение','Заявка отправлена','Писать нельзя','ЧС','Есть в exclude','Отменена исходящая заявка')
          AND NOT EXISTS (SELECT 1 FROM actions f WHERE f.contact_id=c.id AND f.action='friend_request' AND f.result='ok')
          AND NOT EXISTS (SELECT 1 FROM blacklist b WHERE b.id=c.id)
          AND NOT EXISTS (SELECT 1 FROM exclude e WHERE e.id=c.id)
          AND (COALESCE(c.how,'') IN ('Прокомменчен','Был прокомменчен ранее')
               OR EXISTS (SELECT 1 FROM actions ac WHERE ac.contact_id=c.id AND ac.action='comment' AND ac.result='ok'))");
    $st->execute();
    $followupQueue = (int)$st->fetchColumn();
} catch (Throwable $e) { $followupQueue = 0; }

$PROMPT_FIELDS = [
    'prompt_filter'   => ['Промпт фильтрации аккаунтов по специальности',
        'Определяет, наш ли контакт. ИИ возвращает описание деятельности (1–2 слова) — контакт идёт в работу, или строго 0 — контакт отсеивается в «Исключения».'],
    'prompt_comment'  => ['Промпт для комментариев',
        'Что именно ИИ напишет под постом. 0 — комментарий не публикуется, контакт помечается «Дипсик не написал коммент».'],
    'prompt_reply'    => ['Промпт для ответов на входящие сообщения',
        'Как отвечать тем, кто написал в личку первым. Агрессия и спам → «0» (не отвечаем), просьба о взаимных лайках → ответ про лайки + стандартный оффер, остальное → стандартный оффер.'],
    'prompt_message'  => ['Промпт для сообщений в ЛС',
        'Приветствие, которое уходит в личку после того, как человек принял заявку в друзья.'],
    'prompt_profile'  => ['Разбор профиля (шаг перед ЛС)',
        'Внутренний шаг: ИИ собирает профиль человека в JSON (имя, роль, что продаёт), на его основе пишется ЛС.'],
    'prompt_activity' => ['Определение деятельности (модуль «Проверка друзей»)',
        'Определяет деятельность тех, кто добавил вас в друзья. 0 — контакт уходит в «Исключения» (например агрессия).'],
];

function s(PDO $pdo, string $k, string $d = ''): string {
    $st = $pdo->prepare('SELECT value FROM settings WHERE key=?');
    $st->execute([$k]);
    $v = $st->fetchColumn();
    return $v === false || $v === null ? $d : (string)$v;
}
function s_bool(PDO $pdo, string $k, bool $d = false): bool {
    $v = s($pdo, $k, $d ? '1' : '0');
    return in_array(strtolower($v), ['1', 'true', 'да', 'yes', 'on'], true);
}

$runStatus = $pdo->query("SELECT * FROM module_runs WHERE status IN ('running','stopping') ORDER BY id DESC LIMIT 1")->fetch();
$queue = [
    'friends_check' => (int)$pdo->query("SELECT COUNT(*) FROM contacts WHERE COALESCE(friend_status,'') = ''")->fetchColumn(),
    'comments'      => (int)$pdo->query("SELECT COUNT(*) FROM contacts WHERE processed = 0")->fetchColumn(),
    'messages'      => (int)$pdo->query("SELECT COUNT(*) FROM contacts WHERE friend_status = 'Добавился' AND COALESCE(wrote_dm,0) = 0")->fetchColumn(),
    'cleanup'       => null,
];
$lastRuns = [];
foreach ($pdo->query("SELECT * FROM module_runs ORDER BY id DESC LIMIT 60")->fetchAll() as $r) {
    if (!isset($lastRuns[$r['module']])) $lastRuns[$r['module']] = $r;
}
$hours = range(0, 24);
?>
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>VK-лидген · Настройки</title>
<meta name="robots" content="noindex,nofollow">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="assets/style.css?v=<?= @filemtime(__DIR__ . '/assets/style.css') ?>">
</head>
<body>
<header class="top">
  <div class="brand"><span class="dot"></span> VK-лидген <span class="brand-sub">Губтор Балконы</span></div>
  <nav class="tabs">
    <a class="tab" href="?t=contacts">Контакты</a>
    <a class="tab" href="?t=other_contacts">Другие контакты</a>
    <a class="tab" href="?t=exclude">Исключения</a>
    <a class="tab" href="?t=blacklist">Чёрный список</a>
    <a class="tab tab-active" href="?t=settings">Настройки</a>
  </nav>
  <div class="top-right"><a class="btn btn-sm" href="?logout=1">Выйти</a></div>
</header>

<main>
  <?php if ($runStatus): ?>
    <div class="running-bar">
      <span class="pulse"></span>
      Сейчас работает: <strong><?= htmlspecialchars($MODULES[$runStatus['module']][0] ?? $runStatus['module']) ?></strong>
      (<?= htmlspecialchars($runStatus['status'] === 'stopping' ? 'останавливается' : 'выполняется') ?>,
      старт <?= htmlspecialchars(ru_dt((string)$runStatus['started_at'], true)) ?>)
      <button class="btn btn-sm" onclick="stopModule('<?= htmlspecialchars($runStatus['module']) ?>', false)">Остановить</button>
      <button class="btn btn-sm btn-danger" onclick="stopModule('<?= htmlspecialchars($runStatus['module']) ?>', true)">Стоп принудительно</button>
    </div>
  <?php endif; ?>

  <section class="card">
    <?php if ($vkLimited): ?>
      <div class="blocked-banner">
        <strong>ВК ограничил действия до конца дня.</strong>
        Прогоны остановлены и до завтра не запускаются — ВК держит дневной лимит на действия и заявки.
        Причина: <?= htmlspecialchars($vkLimitReason ?: 'flood control') ?>.
        <button class="btn btn-sm" style="margin-left:10px" onclick="unblockVk()">Снять блокировку и разрешить запуск</button>
      </div>
    <?php endif; ?>

    <h2>Запуск модулей</h2>
    <div class="hint">Работает не больше одного модуля одновременно — так безопаснее для аккаунта. Запуск начинается в течение 5 секунд — статус переключится на «работает» сам.</div>
    <div class="hint"><strong>Остановить</strong> — мягко: модуль доделывает текущее действие, записывает статус и завершается (секунды, даже если он сейчас в паузе).
      <strong>Стоп!</strong> — принудительно: процесс снимается сразу, текущее действие может не записаться в базу. Оба действия влияют только на текущий прогон, расписание не меняется.</div>
    <table class="tbl run-tbl">
      <thead><tr>
        <th>Модуль</th><th>Что делает</th><th>Ждут обработки</th><th>Последний запуск</th>
        <th>Контактов за прогон</th><th>Лимит времени, мин</th><th>Запуск и остановка</th>
      </tr></thead>
      <tbody>
      <?php foreach ($MODULES as $m => $info): $last = $lastRuns[$m] ?? null; ?>
        <tr>
          <td><strong><?= htmlspecialchars($info[0]) ?></strong></td>
          <td class="muted"><?= htmlspecialchars($info[1]) ?></td>
          <td class="mono" id="q-<?= $m ?>"><?php
            if ($m === 'friend_followup') { echo (int)$followupQueue; }
            elseif ($m === 'cleanup') { echo '—'; }        // у чистки очередь — сами заявки ВК, не база
            else { echo $queue[$m] === null ? '—' : (int)$queue[$m]; }
          ?></td>
          <td id="st-<?= $m ?>">
            <?php if ($last): ?>
              <span class="badge badge-<?= htmlspecialchars($last['status']) ?>"><?= htmlspecialchars([
                  'queued' => 'в очереди', 'running' => 'работает', 'stopping' => 'останавливается',
                  'done' => 'завершён', 'error' => 'ошибка', 'stopped' => 'остановлен'][$last['status']] ?? $last['status']) ?></span>
              <span class="muted"><?= htmlspecialchars(ru_dt((string)($last['started_at'] ?? ''), true)) ?></span>
            <?php else: ?>
              <span class="muted">ещё не запускался</span>
            <?php endif; ?>
          </td>
          <td><input class="in in-sm" style="min-width:90px" type="number" min="0" value="<?= (int)s($pdo, "limit_$m", '0') ?>"
                     id="lim-<?= $m ?>" title="0 = без ограничения"></td>
          <td>
            <input class="in in-sm" style="min-width:80px" type="number" min="0" value="<?= (int)s($pdo, "run_minutes_$m", '0') ?>"
                   id="min-<?= $m ?>" title="минут на прогон; 0 = пока не кончится окно работы">
            <span class="muted">мин</span>
          </td>
          <td class="row-actions">
            <button class="btn btn-sm btn-primary" onclick="runModule('<?= $m ?>')" title="Поставить модуль в очередь на запуск">Запустить сейчас</button>
            <button class="btn btn-sm" onclick="stopModule('<?= $m ?>', false)"
                    title="Мягкая остановка: модуль закончит текущее действие, запишет статус и завершится. Работает за секунды.">Остановить</button>
            <button class="btn btn-sm btn-danger" onclick="stopModule('<?= $m ?>', true)"
                    title="Принудительно снять процесс сразу. Текущее действие может не записаться в базу.">Стоп!</button>
          </td>
        </tr>
        <?php if ($last && !empty($last['report'])): ?>
        <tr class="report-row"><td colspan="7">
          <details><summary>Отчёт последнего запуска (<?= htmlspecialchars(ru_dt((string)$last['finished_at'], true)) ?>)</summary>
            <pre class="report"><?= htmlspecialchars(ru_dates_in_text((string)$last['report'])) ?></pre></details>
        </td></tr>
        <?php endif; ?>
      <?php endforeach; ?>
      </tbody>
    </table>
    <div class="hint">
      <strong>Ждут обработки</strong> — сколько контактов модуль возьмёт в работу при следующем запуске.
      <strong>Последний запуск</strong> — когда и чем закончился прошлый прогон.
      <strong>Контактов за прогон</strong> — сколько человек обработать за один запуск (0 = все, без ограничения).
      <strong>Лимит времени, мин</strong> — сколько минут модулю разрешено работать в этом прогоне (0 = до конца окна работы из расписания).
      Это разные ограничения: первое — по количеству людей, второе — по времени; можно задать оба сразу.
    </div>
  </section>

  <section class="card">
    <h2>Расписание работы</h2>
    <div class="hint">Для каждого модуля: включать ли автоматический запуск, с какой даты, с какой периодичностью и в какие часы (по Москве) ему разрешено работать.</div>
    <table class="tbl sched-tbl">
      <thead><tr><th>Модуль</th><th>Авто-запуск</th><th>Начать с даты</th><th>Периодичность</th><th>Время запуска (МСК)</th><th>Часы работы (МСК)</th><th>Следующий запуск</th><th></th></tr></thead>
      <tbody>
      <?php foreach ($MODULES as $m => $info): ?>
        <tr>
          <td><strong><?= htmlspecialchars($info[0]) ?></strong></td>
          <td><label class="sw"><input type="checkbox" id="en-<?= $m ?>" <?= s_bool($pdo, "sched_enabled_$m") ? 'checked' : '' ?>> <span>включено</span></label></td>
          <td><input class="in in-sm" type="date" id="dt-<?= $m ?>" value="<?= htmlspecialchars(s($pdo, "sched_start_$m")) ?>"></td>
          <td>
            <select class="in in-sm" id="pd-<?= $m ?>" onchange="document.getElementById('pdw-<?= $m ?>').style.display = this.value==='1' ? 'none' : 'inline-block'">
              <?php $p = (int)s($pdo, "sched_period_$m", '2'); ?>
              <option value="1" <?= $p === 1 ? 'selected' : '' ?>>каждый день</option>
              <option value="custom" <?= $p > 1 ? 'selected' : '' ?>>через N дней</option>
            </select>
            <input class="in in-sm" style="min-width:70px;display:<?= $p > 1 ? 'inline-block' : 'none' ?>" type="number" min="2" max="90"
                   id="pdw-<?= $m ?>" value="<?= max(2, $p) ?>" title="через сколько дней повторять">
          </td>
          <td><input class="in in-sm" type="time" id="tm-<?= $m ?>" value="<?= htmlspecialchars(s($pdo, "sched_time_$m", ['cleanup' => '06:00', 'friends_check' => '08:00', 'messages' => '09:00', 'comments' => '10:00'][$m] ?? '10:00')) ?>"></td>
          <td>
            <select class="in in-sm" id="fr-<?= $m ?>">
              <?php foreach ($hours as $h): if ($h > 23) continue; ?>
                <option value="<?= $h ?>" <?= (int)s($pdo, "sched_from_$m", '9') === $h ? 'selected' : '' ?>><?= sprintf('%02d:00', $h) ?></option>
              <?php endforeach; ?>
            </select>
            —
            <select class="in in-sm" id="to-<?= $m ?>">
              <?php foreach ($hours as $h): if ($h < 1) continue; ?>
                <option value="<?= $h ?>" <?= (int)s($pdo, "sched_to_$m", '20') === $h ? 'selected' : '' ?>><?= sprintf('%02d:00', $h) ?></option>
              <?php endforeach; ?>
            </select>
          </td>
          <td class="mono"><?= htmlspecialchars(next_sched($pdo, $m)) ?></td>
          <td><button class="btn btn-sm btn-primary" onclick="saveSchedule('<?= $m ?>')">Сохранить</button></td>
        </tr>
      <?php endforeach; ?>
      </tbody>
    </table>
  </section>

  <section class="card">
    <h2>Тайминги (построчная обработка с паузами)</h2>
    <div class="hint">Обработка идёт строго по одному контакту: комментарий → пауза → лайк → пауза → заявка в друзья → пауза перед следующим контактом. Значения по умолчанию повторяют ваши n8n-воркфлоу. Часы работы модулей (окно) задаются в расписании ниже.</div>
    <table class="tbl sched-tbl">
      <thead><tr><th>Пауза</th><th>От (сек)</th><th>До (сек)</th><th>Что означает</th></tr></thead>
      <tbody>
        <tr>
          <td><strong>Между действиями</strong></td>
          <td><input class="in in-sm" type="number" min="1" id="p-pause_action_min" value="<?= (int)s($pdo, 'pause_action_min', '10') ?>"></td>
          <td><input class="in in-sm" type="number" min="1" id="p-pause_action_max" value="<?= (int)s($pdo, 'pause_action_max', '30') ?>"></td>
          <td class="muted">комментарий → лайк → заявка в друзья</td>
        </tr>
        <tr>
          <td><strong>Перед следующим контактом</strong></td>
          <td><input class="in in-sm" type="number" min="1" id="p-pause_post_min" value="<?= (int)s($pdo, 'pause_post_min', '300') ?>"></td>
          <td><input class="in in-sm" type="number" min="1" id="p-pause_post_max" value="<?= (int)s($pdo, 'pause_post_max', '360') ?>"></td>
          <td class="muted">после комментария — 5–6 минут, как в n8n</td>
        </tr>
        <tr>
          <td><strong>Между сообщениями в ЛС</strong></td>
          <td><input class="in in-sm" type="number" min="1" id="p-pause_contact_min" value="<?= (int)s($pdo, 'pause_contact_min', '120') ?>"></td>
          <td><input class="in in-sm" type="number" min="1" id="p-pause_contact_max" value="<?= (int)s($pdo, 'pause_contact_max', '180') ?>"></td>
          <td class="muted">рассылка ЛС идёт мягче</td>
        </tr>
        <tr>
          <td><strong>Между отменами заявок</strong></td>
          <td><input class="in in-sm" type="number" min="1" id="p-pause_cleanup_min" value="<?= (int)s($pdo, 'pause_cleanup_min', '3') ?>"></td>
          <td><input class="in in-sm" type="number" min="1" id="p-pause_cleanup_max" value="<?= (int)s($pdo, 'pause_cleanup_max', '8') ?>"></td>
          <td class="muted">чистка заявок: в n8n пауз не было, это страховка от лимитов ВК</td>
        </tr>
        <tr>
          <td><strong>Между заявками в друзья</strong></td>
          <td><input class="in in-sm" type="number" min="1" id="p-pause_friend_min" value="<?= (int)s($pdo, 'pause_friend_min', '15') ?>"></td>
          <td><input class="in in-sm" type="number" min="1" id="p-pause_friend_max" value="<?= (int)s($pdo, 'pause_friend_max', '45') ?>"></td>
          <td class="muted">заявки в друзья и добор: ВК ограничивает их число</td>
        </tr>
        <tr>
          <td><strong>Заявок в друзья в день</strong></td>
          <td><input class="in in-sm" type="number" min="1" max="200" id="p-friend_requests_per_day" value="<?= (int)s($pdo, 'friend_requests_per_day', '50') ?>"></td>
          <td class="muted">—</td>
          <td class="muted">дневной лимит ВК: при упоре сервис останавливается до завтра (по опыту ВК держит ~50)</td>
        </tr>
        <tr>
          <td><strong>Ответов на входящие за прогон</strong></td>
          <td><input class="in in-sm" type="number" min="1" max="100" id="p-limit_replies" value="<?= (int)s($pdo, 'limit_replies', '15') ?>"></td>
          <td><input class="in in-sm" type="number" min="1" max="30" id="p-reply_max_age_days" value="<?= (int)s($pdo, 'reply_max_age_days', '3') ?>"></td>
          <td class="muted">сколько ответить за прогон и насколько свежие сообщения брать (дней)</td>
        </tr>
        <tr>
          <td><strong>Между запросами к ВК</strong></td>
          <td colspan="2"><input class="in in-sm" type="number" step="0.1" min="0.4" id="p-min_request_interval" value="<?= htmlspecialchars(s($pdo, 'min_request_interval', '1.2')) ?>"></td>
          <td class="muted">общий троттлинг, чтобы не ловить лимиты</td>
        </tr>
      </tbody>
    </table>
    <label class="lbl" style="margin-top:12px">Порядок обработки контактов</label>
    <select class="in" style="width:auto;min-width:340px" id="p-process_order">
      <option value="table" <?= s($pdo, 'process_order', 'table') !== 'asc' && s($pdo, 'process_order', 'table') !== 'friends' ? 'selected' : '' ?>>как в таблице — сверху вниз (строка №1 первая)</option>
      <option value="asc" <?= s($pdo, 'process_order') === 'asc' ? 'selected' : '' ?>>по возрастанию ID (кто раньше в базе — тот первый, как было в n8n)</option>
      <option value="friends" <?= s($pdo, 'process_order') === 'friends' ? 'selected' : '' ?>>сначала те, кто добавился в друзья</option>
    </select>
    <div class="hint">В таблице видно, как прогон идёт сверху вниз: текущая строка подсвечивается зелёным, рядом с индикатором написан её номер.</div>

    <div class="save-bar"><button class="btn btn-primary" onclick="saveSettings()">Сохранить тайминги</button>
      <span class="muted">изменения применятся со следующего запуска</span></div>
    <div class="hint">Сколько это по времени: один контакт с комментарием занимает ~6 минут (плюс разбор профиля), поэтому за 14-часовое окно выходит порядка 100–130 комментариев в сутки — столько же, сколько давал ваш n8n.</div>
  </section>

  <section class="card">
    <h2>Промпты и тексты</h2>
    <div class="hint">Подстановки: <code>{NICHE}</code> — специальность из поля ниже, <code>{ABOUT}</code> — позиционирование. Меняйте текст — сервис возьмёт новую версию со следующего запуска.</div>
    <div class="two-col">
      <div>
        <label class="lbl">Специальность / ниша (подставляется как {NICHE})</label>
        <input class="in" style="width:100%" id="p-niche" value="<?= htmlspecialchars(s($pdo, 'niche')) ?>">
        <label class="lbl">Позиционирование владельца (подставляется как {ABOUT})</label>
        <textarea class="ta" id="p-about_owner" rows="3"><?= htmlspecialchars(s($pdo, 'about_owner')) ?></textarea>
      </div>
      <div>
        <label class="lbl">Сколько постов автора комментировать за заход</label>
        <input class="in in-sm" type="number" min="1" max="10" id="p-posts_per_contact" value="<?= (int)s($pdo, 'posts_per_contact', '1') ?>">
        <label class="lbl">Просматривать постов в поисках подходящего</label>
        <input class="in in-sm" type="number" min="1" max="50" id="p-posts_scan" value="<?= (int)s($pdo, 'posts_scan', '10') ?>"
               title="среди постов часто есть записи только с фото (без текста) или с закрытыми комментариями — сервис пропускает их и ищет дальше">
        <label class="lbl" style="margin-top:12px">Модель для ИИ</label>
    <select class="in" style="width:auto;min-width:420px" id="p-llm_provider">
      <option value="portal" <?= s($pdo, 'llm_provider', 'portal') !== 'deepseek' ? 'selected' : '' ?>>бесплатная (Nous Portal) — без оплаты, качество ниже</option>
      <option value="deepseek" <?= s($pdo, 'llm_provider') === 'deepseek' ? 'selected' : '' ?>>DeepSeek — качество заметно выше, платится вашим ключом</option>
    </select>
    <div class="hint">DeepSeek: примерно 1–3 рубля за 100 контактов (модель сначала «думает», потом отвечает).
      Ключ уже прописан в секретах сервиса. Меняется со следующего запуска.</div>

    <label class="lbl" style="margin-top:10px">Строгая проверка ниши</label>
        <select class="in in-sm" id="p-strict_niche">
          <option value="0" <?= !s_bool($pdo, 'strict_niche', false) ? 'selected' : '' ?>>выключена — доверяем ИИ (по умолчанию)</option>
          <option value="1" <?= s_bool($pdo, 'strict_niche', false) ? 'selected' : '' ?>>включена — пропускать только по ключевым словам</option>
        </select>
        <label class="lbl">Ключевые слова ниши (через запятую, основы слов)</label>
        <input class="in" style="width:100%" id="p-niche_keywords" value="<?= htmlspecialchars(s($pdo, 'niche_keywords')) ?>"
               placeholder="например: остеклен, балкон, лодж, окн, забор, бан">
        <label class="lbl" style="margin-top:10px">Чистка заявок: сколько свежих заявок оставлять</label>
        <input class="in in-sm" type="number" min="0" max="1000" id="p-keep_fresh_requests"
               value="<?= (int)s($pdo, 'keep_fresh_requests', '100') ?>" title="Столько самых свежих заявок чистка не трогает">
        <div class="hint">Всегда оставляем указанное число самых новых заявок (по умолчанию 100), а всё, что старше — модуль «Чистка заявок» отменяет. `0` — отменять все заявки.</div>
      </div>
    </div>

    <?php foreach ($PROMPT_FIELDS as $key => $meta): ?>
      <div class="prompt-box">
        <div class="prompt-head">
          <strong><?= htmlspecialchars($meta[0]) ?></strong>
          <button class="btn btn-sm" onclick="resetOne('<?= $key ?>')">вернуть по умолчанию</button>
        </div>
        <div class="hint"><?= htmlspecialchars($meta[1]) ?></div>
        <textarea class="ta mono" id="p-<?= $key ?>" rows="<?= $key === 'prompt_filter' ? 12 : 14 ?>"><?= htmlspecialchars(s($pdo, $key)) ?></textarea>
      </div>
    <?php endforeach; ?>

    <div class="save-bar">
      <button class="btn btn-primary" onclick="saveSettings()">Сохранить настройки</button>
      <button class="btn" onclick="resetAll()">Вернуть все промпты по умолчанию</button>
      <span class="muted" id="save-msg"></span>
    </div>
  </section>

  <section class="card">
    <h2>История запусков</h2>
    <table class="tbl">
      <thead><tr><th>#</th><th>Модуль</th><th>Источник</th><th>Статус</th><th>Старт</th><th>Финиш</th><th>Код</th><th>Лимит</th></tr></thead>
      <tbody>
      <?php foreach ($pdo->query("SELECT * FROM module_runs ORDER BY id DESC LIMIT 25")->fetchAll() as $r): ?>
        <tr>
          <td class="mono"><?= (int)$r['id'] ?></td>
          <td><?= htmlspecialchars($MODULES[$r['module']][0] ?? $r['module']) ?></td>
          <td><?= $r['source'] === 'manual' ? 'вручную' : 'по расписанию' ?></td>
          <td><?= htmlspecialchars((string)$r['status']) ?></td>
          <td class="mono"><?= htmlspecialchars(ru_dt((string)$r['started_at'], true)) ?></td>
          <td class="mono"><?= htmlspecialchars(ru_dt((string)$r['finished_at'], true)) ?></td>
          <td class="mono"><?= htmlspecialchars((string)($r['exit_code'] ?? '')) ?></td>
          <td class="mono"><?= (int)$r['limit_n'] ?></td>
        </tr>
      <?php endforeach; ?>
      </tbody>
    </table>
  </section>
</main>

<script>
const MODS = <?= json_encode(array_keys($MODULES)) ?>;

async function api(action, body) {
  const fd = new FormData();
  fd.append('table', 'contacts');
  Object.entries(body || {}).forEach(([k, v]) => fd.append(k, v));
  const r = await fetch('?a=' + action, { method: 'POST', body: fd });
  try { return await r.json(); } catch (e) { return { ok: false, error: 'ошибка сервера' }; }
}
function toast(msg, ok = true) {
  let t = document.getElementById('toast');
  if (!t) { t = document.createElement('div'); t.id = 'toast'; document.body.appendChild(t); }
  t.textContent = msg;
  t.className = 'toast ' + (ok ? 'toast-ok' : 'toast-err');
  t.hidden = false;
  clearTimeout(t._tm);
  t._tm = setTimeout(() => { t.hidden = true; }, 4000);
}
async function runModule(m) {
  const lim = (document.getElementById('lim-' + m).value || '0').trim();
  const mins = (document.getElementById('min-' + m).value || '0').trim();
  if (lim === '0' || lim === '') {
    const q = document.getElementById('q-' + m) ? document.getElementById('q-' + m).textContent.trim() : '';
    const msg = 'Лимит контактов не указан — модуль будет работать, пока не закончатся контакты' +
      (q && q !== '—' ? ' (в очереди ' + q + ')' : '') + ' или пока не выйдет время работы' +
      (mins !== '0' && mins !== '' ? ' (' + mins + ' мин)' : ' (до конца окна расписания)') +
      '. Запускать?';
    if (!confirm(msg)) return;
  }
  const res = await api('run_now', { module: m, limit: lim });
  if (res.ok) { toast('Запуск поставлен в очередь. Начнётся в течение 5 секунд.'); setTimeout(() => location.reload(), 7000); }
  else toast(res.error || 'не получилось', false);
}
async function stopModule(m, force) {
  if (force && !confirm('Снять процесс принудительно? Текущее действие может не записаться в базу.')) return;
  const res = await api(force ? 'force_stop' : 'stop_run', { module: m });
  toast(res.ok ? (res.note || 'ок') : (res.error || 'не получилось'), res.ok);
  setTimeout(() => location.reload(), 1500);
}
async function saveSchedule(m) {
  const pd = document.getElementById('pd-' + m).value;
  const period = pd === '1' ? 1 : document.getElementById('pdw-' + m).value;
  const res = await api('schedule_save', {
    module: m,
    enabled: document.getElementById('en-' + m).checked ? '1' : '0',
    start: document.getElementById('dt-' + m).value,
    period: period,
    from: document.getElementById('fr-' + m).value,
    to: document.getElementById('to-' + m).value,
    time: document.getElementById('tm-' + m).value,
  });
  toast(res.ok ? 'Расписание сохранено' : (res.error || 'не сохранилось'), res.ok);
}
function collect() {
  const data = {
    niche: document.getElementById('p-niche').value,
    about_owner: document.getElementById('p-about_owner').value,
    posts_per_contact: document.getElementById('p-posts_per_contact').value,
    keep_fresh_requests: document.getElementById('p-keep_fresh_requests').value,
    strict_niche: document.getElementById('p-strict_niche').value,
    niche_keywords: document.getElementById('p-niche_keywords').value,
    pause_action_min: document.getElementById('p-pause_action_min').value,
    pause_action_max: document.getElementById('p-pause_action_max').value,
    pause_post_min: document.getElementById('p-pause_post_min').value,
    pause_post_max: document.getElementById('p-pause_post_max').value,
    pause_contact_min: document.getElementById('p-pause_contact_min').value,
    pause_contact_max: document.getElementById('p-pause_contact_max').value,
    min_request_interval: document.getElementById('p-min_request_interval').value,
    process_order: document.getElementById('p-process_order').value,
    pause_cleanup_min: document.getElementById('p-pause_cleanup_min').value,
    pause_friend_min: document.getElementById('p-pause_friend_min').value,
    pause_friend_max: document.getElementById('p-pause_friend_max').value,
    friend_requests_per_day: document.getElementById('p-friend_requests_per_day').value,
    limit_replies: document.getElementById('p-limit_replies').value,
    reply_max_age_days: document.getElementById('p-reply_max_age_days').value,
    pause_cleanup_max: document.getElementById('p-pause_cleanup_max').value,
    posts_scan: document.getElementById('p-posts_scan').value,
    llm_provider: document.getElementById('p-llm_provider').value,
  };
  MODS.forEach(m => {
  data['limit_' + m] = document.getElementById('lim-' + m).value;
  data['run_minutes_' + m] = document.getElementById('min-' + m).value;
  });
  ['prompt_filter', 'prompt_comment', 'prompt_message', 'prompt_profile', 'prompt_activity'].forEach(k => {
    const el = document.getElementById('p-' + k);
    if (el) data[k] = el.value;
  });
  return data;
}
async function saveSettings() {
  const res = await api('settings_save', { data: JSON.stringify(collect()) });
  document.getElementById('save-msg').textContent = res.ok ? 'сохранено ' + new Date().toLocaleTimeString() : (res.error || '');
  toast(res.ok ? 'Настройки сохранены' : (res.error || 'не сохранилось'), res.ok);
}
async function resetAll() {
  if (!confirm('Вернуть все промпты к значениям по умолчанию?')) return;
  const res = await api('prompts_reset', {});
  if (res.ok) { toast('Промпты сброшены'); setTimeout(() => location.reload(), 900); }
  else toast(res.error || 'не получилось', false);
}
async function resetOne(key) {
  const defs = await (await fetch('data/prompt_defaults.json?t=' + Date.now())).json().catch(() => null);
  if (!defs || !defs[key]) { toast('файл значений по умолчанию недоступен', false); return; }
  document.getElementById('p-' + key).value = defs[key];
  toast('Подставлен текст по умолчанию — не забудьте сохранить');
}
async function pollStatus() {
  const res = await api('runs_status', {});
  if (!res.ok) return;
  const anyRunning = !!res.running;
  document.querySelector('.running-bar')?.remove();
  for (const [m, st] of Object.entries(res.modules)) {
    const cell = document.getElementById('st-' + m);
    if (!cell) continue;
    if (st.is_running) {
      cell.innerHTML = '<span class="badge badge-running">работает</span> <span class="muted">' + (st.started || '') + '</span>';
    }
  }
  if (anyRunning && !document.querySelector('.running-bar')) location.reload();
}
setInterval(pollStatus, 10000);
</script>
</body>
</html>
