// app-views.js — Web UI part 3/3 (cron editor, tools, terminal, command palette, theme, setup wizard, reliability) + startup init. Continuation; MUST load last.

function parseCronToFields(schedule) {
  const intMatch = schedule.match(/^(\d+)\s*(m|min|h|hr|d|day|s|sec)s?$/i);
  if (intMatch) {
    const val = intMatch[1];
    const u = intMatch[2].toLowerCase();
    const unit = (u === 'm' || u === 'min') ? 'min' : (u === 'h' || u === 'hr') ? 'h' : (u === 'd' || u === 'day') ? 'd' : 's';
    return { mode: 'interval', interval: val, intervalUnit: unit, hour: '08', minute: '00', weekdays: [], monthday: '1' };
  }
  const parts = schedule.trim().split(/\s+/);
  if (parts.length === 5) {
    const [min, hour, day, , wd] = parts;
    const weekdays = wd !== '*' ? wd.split(',').flatMap(v => {
      if (v.includes('-')) { const [a,b] = v.split('-').map(Number); const r=[]; for(let i=a;i<=b;i++) r.push(String(i)); return r; }
      return [v];
    }) : [];
    let mode = 'daily';
    if (weekdays.length > 0) mode = 'weekly';
    if (day !== '*') mode = 'monthly';
    return { mode, interval: '5', intervalUnit: 'min', hour: hour === '*' ? '08' : hour.padStart(2,'0'), minute: min === '*' ? '00' : min.padStart(2,'0'), weekdays, monthday: day === '*' ? '1' : day };
  }
  return { mode: 'daily', interval: '5', intervalUnit: 'min', hour: '08', minute: '00', weekdays: [], monthday: '1' };
}

function fieldsToCron(id) {
  const pfx = id ? id + '-' : 'create-';
  const mode = document.querySelector(`input[name="sched-mode-${pfx}"]:checked`)?.value || 'daily';
  const oneShot = document.querySelector(`input[name="sched-recur-${pfx}"]:checked`)?.value === 'true';

  if (mode === 'interval') {
    const val = document.getElementById(`sched-interval-${pfx}`)?.value || '5';
    const unit = document.getElementById(`sched-interval-unit-${pfx}`)?.value || 'min';
    const unitMap = { min: 'm', h: 'h', d: 'd', s: 's' };
    return { schedule: val + (unitMap[unit] || 'm'), oneShot };
  }

  const hour = document.getElementById(`sched-hour-${pfx}`)?.value || '8';
  const minute = document.getElementById(`sched-minute-${pfx}`)?.value || '0';

  if (mode === 'weekly') {
    const checks = document.querySelectorAll(`input[name="sched-wd-${pfx}"]:checked`);
    const days = Array.from(checks).map(c => c.value);
    if (days.length === 0) { toast(t('cron.schedule.weekday.min'), 'error'); return null; }
    return { schedule: `${minute} ${hour} * * ${days.join(',')}`, oneShot };
  }

  if (mode === 'monthly') {
    const day = document.getElementById(`sched-monthday-${pfx}`)?.value || '1';
    return { schedule: `${minute} ${hour} ${day} * *`, oneShot };
  }

  return { schedule: `${minute} ${hour} * * *`, oneShot };
}

function buildScheduleEditor(id, schedule, oneShot, hideButtons) {
  const f = parseCronToFields(schedule || '0 8 * * *');
  const pfx = id ? id + '-' : 'create-';
  const wdNames = t('cron.weekdays').split(',');
  const modeOptions = [
    { val: 'interval', label: `${icon('timer', 14)} ${t('cron.schedule.interval')}` },
    { val: 'daily', label: `${icon('calendar', 14)} ${t('cron.schedule.daily')}` },
    { val: 'weekly', label: `${icon('calendar', 14)} ${t('cron.schedule.weekly')}` },
    { val: 'monthly', label: `${icon('calendar', 14)} ${t('cron.schedule.monthly')}` },
  ];

  return `
    <div style="margin-bottom:10px">
      <div style="font-size:0.82em;color:var(--fg2);margin-bottom:6px;font-weight:500">${t('cron.schedule.repeat')}</div>
      <div style="display:flex;gap:4px;flex-wrap:wrap">
        ${modeOptions.map(o => `<label style="display:flex;align-items:center;gap:4px;padding:4px 10px;border-radius:6px;cursor:pointer;font-size:0.82em;background:${f.mode===o.val?'var(--accent)':'var(--bg2)'};color:${f.mode===o.val?'#fff':'var(--fg)'}">
          <input type="radio" name="sched-mode-${pfx}" value="${o.val}" ${f.mode===o.val?'checked':''} onchange="toggleSchedFields('${pfx}')" style="display:none"> ${o.label}
        </label>`).join('')}
      </div>
    </div>

    <div id="sched-interval-row-${pfx}" style="display:${f.mode==='interval'?'flex':'none'};gap:6px;align-items:center;margin-bottom:8px">
      <span style="font-size:0.82em;color:var(--fg2)">${t('cron.schedule.every')}</span>
      <input id="sched-interval-${pfx}" type="number" min="1" value="${f.interval}" class="input" style="width:60px;text-align:center">
      <select id="sched-interval-unit-${pfx}" class="input" style="width:auto">
        <option value="s" ${f.intervalUnit==='s'?'selected':''}>${t('cron.units.seconds')}</option>
        <option value="min" ${f.intervalUnit==='min'?'selected':''}>${t('cron.units.minutes')}</option>
        <option value="h" ${f.intervalUnit==='h'?'selected':''}>${t('cron.units.hours')}</option>
        <option value="d" ${f.intervalUnit==='d'?'selected':''}>${t('cron.units.days')}</option>
      </select>
    </div>

    <div id="sched-time-row-${pfx}" style="display:${f.mode!=='interval'?'flex':'none'};gap:6px;align-items:center;margin-bottom:8px">
      <span style="font-size:0.82em;color:var(--fg2)">${t('cron.schedule.at')}</span>
      <input id="sched-hour-${pfx}" type="number" min="0" max="23" value="${f.hour}" class="input" style="width:50px;text-align:center">
      <span style="font-size:1.1em;font-weight:600">:</span>
      <input id="sched-minute-${pfx}" type="number" min="0" max="59" value="${f.minute}" class="input" style="width:50px;text-align:center">
      <span style="font-size:0.82em;color:var(--fg2)">${t('cron.schedule.oclock')}</span>
    </div>

    <div id="sched-wd-row-${pfx}" style="display:${f.mode==='weekly'?'flex':'none'};gap:4px;flex-wrap:wrap;margin-bottom:8px">
      ${wdNames.map((d,i) => `<label style="display:flex;align-items:center;gap:2px;padding:4px 8px;border-radius:6px;cursor:pointer;font-size:0.82em;background:${f.weekdays.includes(String(i))?'var(--accent)':'var(--bg2)'};color:${f.weekdays.includes(String(i))?'#fff':'var(--fg)'}">
        <input type="checkbox" name="sched-wd-${pfx}" value="${i}" ${f.weekdays.includes(String(i))?'checked':''} onchange="this.parentElement.style.background=this.checked?'var(--accent)':'var(--bg2)';this.parentElement.style.color=this.checked?'#fff':'var(--fg)'" style="display:none"> ${d}
      </label>`).join('')}
    </div>

    <div id="sched-md-row-${pfx}" style="display:${f.mode==='monthly'?'flex':'none'};gap:6px;align-items:center;margin-bottom:8px">
      <span style="font-size:0.82em;color:var(--fg2)">${t('cron.schedule.onday')}</span>
      <input id="sched-monthday-${pfx}" type="number" min="1" max="31" value="${f.monthday}" class="input" style="width:55px;text-align:center">
      <span style="font-size:0.82em;color:var(--fg2)">${t('cron.schedule.ofmonth')}</span>
    </div>

    <div style="display:flex;gap:6px;align-items:center;margin-bottom:8px">
      <span style="font-size:0.82em;color:var(--fg2)">${t('cron.schedule.type')}:</span>
      <label style="font-size:0.82em;cursor:pointer"><input type="radio" name="sched-recur-${pfx}" value="false" ${!oneShot?'checked':''}> ${icon('refresh-cw', 12)} ${t('cron.recurring')}</label>
      <label style="font-size:0.82em;cursor:pointer"><input type="radio" name="sched-recur-${pfx}" value="true" ${oneShot?'checked':''}> ${icon('zap', 12)} ${t('cron.single')}</label>
    </div>

    ${id ? `<div style="display:flex;gap:6px">
      <button class="btn btn-sm" onclick="saveCronSchedule('${id}')">${icon('save', 12)} ${t('save')}</button>
      <button class="btn btn-sm btn-outline" onclick="document.getElementById('cron-edit-${id}').style.display='none'">${t('cancel')}</button>
    </div>` : ''}`;
}

function toggleSchedFields(pfx) {
  const mode = document.querySelector(`input[name="sched-mode-${pfx}"]:checked`)?.value || 'daily';
  document.getElementById('sched-interval-row-' + pfx).style.display = mode === 'interval' ? 'flex' : 'none';
  document.getElementById('sched-time-row-' + pfx).style.display = mode !== 'interval' ? 'flex' : 'none';
  document.getElementById('sched-wd-row-' + pfx).style.display = mode === 'weekly' ? 'flex' : 'none';
  document.getElementById('sched-md-row-' + pfx).style.display = mode === 'monthly' ? 'flex' : 'none';
}

function editCronSchedule(id) {
  const el = document.getElementById('cron-edit-' + id);
  el.style.display = el.style.display === 'none' ? '' : 'none';
}

async function saveCronSchedule(id) {
  const result = fieldsToCron(id);
  if (!result) return;
  const res = await fetch(API + '/api/cron/update', {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ id, schedule: result.schedule, oneShot: result.oneShot }),
  });
  const data = await res.json();
  if (data.ok) { toast(t('cron.updated')); loadCron(); }
  else toast(data.error || t('error'), 'error');
}

async function runCronJob(id) {
  toast(t('cron.executing'));
  const res = await fetch(API + '/api/cron/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id }) });
  const data = await res.json();
  if (data.error) toast(data.error, 'error');
  else toast(t('cron.executed'));
  loadCron();
}

// ── Tools ───────────────────────────────────────────────
let allTools = [];

async function loadTools() {
  const res = await fetch(API + '/api/tools');
  const data = await res.json();
  allTools = data.tools || [];
  document.getElementById('tools-count').textContent = t('tools.count', { count: allTools.length });
  renderTools(allTools);
}

function filterTools() {
  const q = document.getElementById('tools-search').value.toLowerCase();
  const filtered = q ? allTools.filter(t => t.name.toLowerCase().includes(q) || t.description.toLowerCase().includes(q)) : allTools;
  document.getElementById('tools-count').textContent = t('tools.count', { count: filtered.length });
  renderTools(filtered);
}

function renderTools(tools) {
  const categories = {};
  tools.forEach(tl => {
    const cat = categorize(tl.name);
    if (!categories[cat]) categories[cat] = [];
    categories[cat].push(tl);
  });

  const catIcons = {
    'system': 'monitor', 'email': 'mail', 'automation': 'sparkles',
    'pdf': 'file-text', 'dev': 'code', 'network': 'globe',
    'media': 'image', 'clipboard': 'clipboard', 'files': 'folder', 'other': 'hammer'
  };

  let html = '';
  for (const [cat, catTools] of Object.entries(categories)) {
    const catIcon = catIcons[cat] || 'hammer';
    html += `<div style="margin-bottom:20px"><h3 style="font-size:0.85em;color:var(--fg2);margin-bottom:8px;display:flex;align-items:center;gap:6px">${icon(catIcon, 16)} ${t('tools.cat.' + cat)}</h3>`;
    html += catTools.map(tl => {
      const params = Object.keys(tl.parameters || {});
      const paramBadges = params.map(p => `<span class="badge" style="font-size:0.65em">${p}</span>`).join(' ');
      return `<div class="list-item" style="cursor:pointer" onclick="runTool('${escapeHtml(tl.name)}')">
        <div class="info">
          <div class="name" style="font-family:monospace;font-size:0.85em">${tl.name} ${paramBadges}</div>
          <div class="desc">${tl.description}</div>
        </div>
        <button class="btn btn-sm btn-outline" onclick="event.stopPropagation();runTool('${escapeHtml(tl.name)}')">${icon('play', 12)}</button>
      </div>`;
    }).join('');
    html += '</div>';
  }
  document.getElementById('tools-list').innerHTML = html || `<div class="card"><h3>${t('tools.none')}</h3><div class="sub">${t('tools.none.desc')}</div></div>`;
}

function categorize(name) {
  if (['run_shell','sudo_command','system_info','volume_set','brightness_set','bluetooth_control','wifi_status','say_text','notify','process_list','kill_process'].includes(name)) return 'system';
  if (name.startsWith('email_')) return 'email';
  if (['osascript','osascript_js','cliclick_type','cliclick_click','cliclick_key'].includes(name)) return 'automation';
  if (name.startsWith('pdf_') || name.includes('_to_pdf')) return 'pdf';
  if (['git_status','git_commit','pm2_status','pm2_restart','pm2_logs','ssh_command','docker_ps'].includes(name)) return 'dev';
  if (['web_fetch','network_check','open_url'].includes(name)) return 'network';
  if (['image_convert','image_resize','ffmpeg_convert','whisper_transcribe','screenshot'].includes(name)) return 'media';
  if (['clipboard_get','clipboard_set'].includes(name)) return 'clipboard';
  if (['find_files','disk_usage','open_file','calendar_today','calendar_upcoming'].includes(name)) return 'files';
  return 'other';
}

function runTool(name) {
  const tool = allTools.find(t => t.name === name);
  if (!tool) return;
  const params = Object.entries(tool.parameters || {});
  if (params.length === 0) { executeTool(name, {}); return; }
  const values = {};
  for (const [key, def] of params) {
    const val = prompt(`${key}: ${def.description}`, '');
    if (val === null) return;
    if (val) values[key] = val;
  }
  executeTool(name, values);
}

async function executeTool(name, params) {
  toast(t('tools.running', { name }));
  try {
    const res = await fetch(API + '/api/tools/execute', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name, params }),
    });
    const data = await res.json();
    if (data.error) { toast(data.error, 'error'); return; }
    const output = data.output || '(no output)';
    document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
    document.querySelector('[data-page="terminal"]').classList.add('active');
    document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
    document.getElementById('page-terminal').classList.add('active');
    document.getElementById('page-title').innerHTML = `${icon('terminal', 18)} ${t('nav.terminal')}`;
    const termOutput = document.getElementById('terminal-output');
    termOutput.innerHTML += `<div class="term-cmd">${icon('wrench', 12)} ${escapeHtml(name)} ${JSON.stringify(params)}</div>`;
    termOutput.innerHTML += `<div>${escapeHtml(output)}</div>`;
    termOutput.scrollTop = termOutput.scrollHeight;
    toast(t('tools.executed'));
  } catch (err) {
    toast(t('error') + ': ' + err.message, 'error');
  }
}

// ── Terminal ────────────────────────────────────────────
const termHist = []; let termIdx = -1;
async function runCommand() {
  const input = document.getElementById('terminal-input');
  const output = document.getElementById('terminal-output');
  const cmd = input.value.trim();
  if (!cmd) return;
  termHist.unshift(cmd); termIdx = -1; input.value = '';
  output.innerHTML += `<div class="term-cmd">$ ${escapeHtml(cmd)}</div>`;
  try {
    const res = await fetch(API + '/api/terminal', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ command: cmd }),
    });
    const data = await res.json();
    if (data.output) output.innerHTML += `<div class="${data.exitCode ? 'term-err' : ''}">${escapeHtml(data.output)}</div>`;
  } catch (err) { output.innerHTML += `<div class="term-err">${t('error')}: ${err.message}</div>`; }
  output.scrollTop = output.scrollHeight;
}

document.getElementById('terminal-input').addEventListener('keydown', (e) => {
  if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); runCommand(); }
  if (e.key === 'ArrowUp') { e.preventDefault(); if (termIdx < termHist.length-1) e.target.value = termHist[++termIdx]; }
  if (e.key === 'ArrowDown') { e.preventDefault(); termIdx > 0 ? e.target.value = termHist[--termIdx] : (termIdx=-1, e.target.value=''); }
});

// ── Maintenance ─────────────────────────────────────────
async function loadMaintenance() {
  const [doctorRes, backupRes] = await Promise.all([
    fetch(API + '/api/doctor'),
    fetch(API + '/api/backups'),
  ]);
  const doctorData = await doctorRes.json();
  const backupData = await backupRes.json();

  let html = '';

  const healthIcon = doctorData.healthy ? icon('circle-check', 24, 'style="color:var(--green)"') : (doctorData.errorCount > 0 ? icon('circle-x', 24, 'style="color:var(--red)"') : icon('circle-alert', 24, 'style="color:var(--yellow)"'));
  html += `<div class="card" style="margin-bottom:16px">
    <div style="display:flex;align-items:center;gap:10px;margin-bottom:12px">
      ${healthIcon}
      <div style="flex:1">
        <h3 style="font-size:0.95em;text-transform:none;letter-spacing:0;display:flex;align-items:center;gap:6px">${icon('stethoscope', 18)} ${t('maint.doctor')}</h3>
        <div class="sub">${t('maint.doctor.errors', { errors: doctorData.errorCount, warns: doctorData.warnCount })}</div>
      </div>
      <button class="btn btn-sm btn-outline" onclick="loadMaintenance()">${icon('refresh-cw', 12)} ${t('maint.doctor.check')}</button>
      ${doctorData.errorCount > 0 ? `<button class="btn btn-sm" onclick="repairAll()">${icon('wrench', 12)} ${t('maint.doctor.fix.all')}</button>` : ''}
    </div>`;

  for (const issue of doctorData.issues) {
    const sevIcons = { error: icon('circle-x', 14), warning: icon('alert-triangle', 14), info: icon('info', 14) };
    const colors = { error: 'var(--red)', warning: 'var(--yellow)', info: 'var(--fg2)' };
    html += `<div style="display:flex;align-items:center;gap:8px;padding:6px 0;font-size:0.85em;border-top:1px solid var(--glass-border)">
      <span style="color:${colors[issue.severity]}">${sevIcons[issue.severity]}</span>
      <span style="flex:1"><strong>${issue.category}:</strong> ${issue.message}</span>
      ${issue.fixAction ? `<button class="btn btn-sm btn-outline" onclick="repairIssue('${issue.fixAction}')" title="${issue.fix || ''}">${icon('wrench', 12)} Fix</button>` : ''}
    </div>`;
  }
  html += `</div>`;

  // Backup & Restore
  html += `<div class="card" style="margin-bottom:16px">
    <div style="display:flex;align-items:center;gap:10px;margin-bottom:12px">
      <span style="display:flex">${icon('hard-drive', 24)}</span>
      <div style="flex:1">
        <h3 style="font-size:0.95em;text-transform:none;letter-spacing:0">${t('maint.backup')}</h3>
        <div class="sub">${t('maint.backup.desc')}</div>
      </div>
      <button class="btn btn-sm" onclick="createBackupMaint()">${icon('save', 12)} ${t('maint.backup.create')}</button>
    </div>`;

  if (backupData.backups.length > 0) {
    for (const b of backupData.backups) {
      const date = new Date(b.createdAt).toLocaleString(getLang() === 'de' ? 'de-DE' : 'en-US');
      const size = b.size < 1024 ? b.size + ' B' : (b.size / 1024).toFixed(1) + ' KB';
      html += `<div style="display:flex;align-items:center;gap:8px;padding:8px 0;border-top:1px solid var(--glass-border);font-size:0.85em">
        ${icon('package', 16)}
        <div style="flex:1">
          <div style="font-weight:500;font-family:monospace">${b.id}</div>
          <div style="color:var(--fg2);font-size:0.82em">${date} · ${b.fileCount} ${t('maint.backup.files')} · ${size}</div>
        </div>
        <button class="btn btn-sm btn-outline" onclick="showBackupFiles('${b.id}')">${icon('clipboard', 12)} ${t('maint.backup.files')}</button>
        <button class="btn btn-sm btn-outline" onclick="restoreBackup('${b.id}')">${icon('refresh-cw', 12)} ${t('maint.backup.restore')}</button>
        <button class="btn btn-sm btn-outline" style="color:var(--red)" onclick="deleteBackup('${b.id}')">${icon('trash-2', 12)}</button>
      </div>`;
    }
  } else {
    html += `<div style="font-size:0.85em;color:var(--fg2);padding:8px 0;border-top:1px solid var(--glass-border)">${t('maint.backup.none')}</div>`;
  }
  html += `<div id="backup-files-area" style="display:none;margin-top:8px;padding:8px;background:var(--bg3);border-radius:6px;font-size:0.82em"></div></div>`;

  // PM2
  html += `<div class="card" style="margin-bottom:16px">
    <div style="display:flex;align-items:center;gap:10px;margin-bottom:12px">
      <span style="display:flex">${icon('settings', 24)}</span>
      <div style="flex:1">
        <h3 style="font-size:0.95em;text-transform:none;letter-spacing:0">${t('maint.pm2')}</h3>
        <div class="sub">${t('maint.pm2.desc')}</div>
      </div>
      <button class="btn btn-sm btn-outline" onclick="refreshPM2Status()">${icon('refresh-cw', 12)} ${t('maint.pm2.status')}</button>
    </div>
    <div id="pm2-status" style="margin-bottom:12px;font-size:0.85em;color:var(--fg2)">${t('maint.pm2.loading')}</div>
    <div style="display:flex;gap:8px;flex-wrap:wrap">
      <button class="btn btn-sm" onclick="pm2Action('restart')">${icon('refresh-cw', 12)} ${t('maint.pm2.restart')}</button>
      <button class="btn btn-sm btn-outline" onclick="pm2Action('reload')">${icon('refresh-cw', 12)} ${t('maint.pm2.reload')}</button>
      <button class="btn btn-sm btn-danger" onclick="pm2Action('stop')">${icon('pause', 12)} ${t('maint.pm2.stop')}</button>
      <button class="btn btn-sm" style="background:var(--green)" onclick="pm2Action('start')">${icon('play', 12)} ${t('maint.pm2.start')}</button>
      <button class="btn btn-sm btn-outline" onclick="pm2Action('flush')">${icon('trash-2', 12)} ${t('maint.pm2.flush')}</button>
    </div>
    <div id="pm2-logs" style="display:none;margin-top:12px"></div>
  </div>`;

  // Logs
  html += `<div class="card" style="margin-bottom:16px">
    <div style="display:flex;align-items:center;gap:10px;margin-bottom:12px">
      <span style="display:flex">${icon('clipboard', 24)}</span>
      <div style="flex:1">
        <h3 style="font-size:0.95em;text-transform:none;letter-spacing:0">${t('maint.logs')}</h3>
        <div class="sub">${t('maint.logs.desc')}</div>
      </div>
      <button class="btn btn-sm btn-outline" onclick="loadPM2Logs()">${icon('refresh-cw', 12)} ${t('maint.logs.refresh')}</button>
    </div>
    <div id="pm2-log-output" style="background:var(--bg);border-radius:6px;padding:10px;font-family:monospace;font-size:0.75em;max-height:300px;overflow-y:auto;white-space:pre-wrap;color:var(--fg2);border:1px solid var(--glass-border)">${t('maint.logs.load')}</div>
  </div>`;

  document.getElementById('maintenance-content').innerHTML = html;
  refreshPM2Status();
  if (window._pm2RefreshInterval) clearInterval(window._pm2RefreshInterval);
  window._pm2RefreshInterval = setInterval(() => {
    if (document.getElementById('pm2-status')) refreshPM2Status();
    else clearInterval(window._pm2RefreshInterval);
  }, 10_000);
}

// ── WhatsApp Groups Management ──────────────────────────
let _waGroupsCache = null;
let _waRulesCache = null;

async function loadWAGroups() {
  const container = document.getElementById('wa-groups-content');
  if (!container) return;
  container.innerHTML = `<div style="color:var(--fg2);font-size:0.85em">${t('wa.groups.loading')}</div>`;

  const [groupsRes, rulesRes] = await Promise.all([
    fetch(API + '/api/whatsapp/groups').then(r => r.json()).catch(() => ({ groups: [], error: 'Unreachable' })),
    fetch(API + '/api/whatsapp/group-rules').then(r => r.json()).catch(() => ({ rules: [] })),
  ]);

  _waGroupsCache = groupsRes.groups || [];
  _waRulesCache = rulesRes.rules || [];
  const activeCount = _waRulesCache.filter(r => r.enabled).length;

  const badge = document.getElementById('wa-groups-badge');
  if (badge) badge.textContent = activeCount > 0 ? `(${t('wa.groups.active', { count: activeCount })})` : '';

  if (groupsRes.error && _waGroupsCache.length === 0) {
    container.innerHTML = `<div style="color:var(--fg2);font-size:0.85em;padding:8px 0">${t('wa.not.connected')}</div>`;
    return;
  }

  const rulesMap = {};
  for (const r of _waRulesCache) rulesMap[r.groupId] = r;

  let html = `<div style="display:flex;align-items:center;gap:8px;margin-bottom:10px">
    <div style="flex:1;font-size:0.8em;color:var(--fg2)">${t('wa.groups.count', { count: _waGroupsCache.length })} · ${t('wa.groups.active', { count: activeCount })}</div>
    <button class="btn btn-sm btn-outline" style="font-size:0.75em" onclick="loadWAGroups()">${icon('refresh-cw', 12)}</button>
  </div>`;

  const sorted = [..._waGroupsCache].sort((a, b) => {
    const aRule = rulesMap[a.id]; const bRule = rulesMap[b.id];
    if (aRule?.enabled && !bRule?.enabled) return -1;
    if (!aRule?.enabled && bRule?.enabled) return 1;
    if (aRule && !bRule) return -1;
    if (!aRule && bRule) return 1;
    return a.name.localeCompare(b.name);
  });

  for (const g of sorted) {
    const rule = rulesMap[g.id];
    const isEnabled = rule?.enabled;
    const statusIcon = isEnabled ? icon('circle-check', 14, 'style="color:var(--green)"') : icon('circle-dot', 14, 'style="opacity:0.3"');
    const allowedCount = rule?.allowedParticipants?.length || 0;
    const accessLabel = !rule ? t('wa.groups.no.config') :
      isEnabled ? (allowedCount > 0 ? t('wa.groups.allowed', { count: allowedCount }) : t('wa.groups.all.allowed')) :
      t('disabled');
    const approvalLabel = rule?.requireApproval !== false ? t('wa.approval') : t('wa.auto');

    html += `<div style="display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid var(--glass-border)">
      ${statusIcon}
      <div style="flex:1;min-width:0">
        <div style="font-weight:500;font-size:0.85em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${escapeHtml(g.name)}</div>
        <div style="font-size:0.72em;color:var(--fg2)">${accessLabel}${isEnabled ? ' · ' + approvalLabel : ''}</div>
      </div>
      <button class="btn btn-sm ${isEnabled ? '' : 'btn-outline'}" style="font-size:0.75em;padding:4px 8px" onclick="toggleWAGroup('${g.id}', '${escapeHtml(g.name)}', ${!isEnabled})">
        ${isEnabled ? icon('pause', 12) : icon('play', 12)}
      </button>
      <button class="btn btn-sm btn-outline" style="font-size:0.75em;padding:4px 8px" onclick="configureWAGroup('${g.id}', '${escapeHtml(g.name)}')">${icon('settings', 12)}</button>
    </div>`;
  }

  container.innerHTML = html;
}

async function toggleWAGroup(groupId, groupName, enable) {
  await fetch(API + '/api/whatsapp/group-rules', {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ groupId, groupName, enabled: enable }),
  });
  toast(enable ? t('wa.groups.enabled', { name: groupName }) : t('wa.groups.disabled', { name: groupName }));
  loadWAGroups();
}

async function configureWAGroup(groupId, groupName) {
  const container = document.getElementById('wa-groups-content');
  if (!container) return;

  const rule = _waRulesCache?.find(r => r.groupId === groupId) || {};
  container.innerHTML = `<div style="color:var(--fg2);font-size:0.85em">${t('loading')}</div>`;
  const res = await fetch(API + `/api/whatsapp/groups/${encodeURIComponent(groupId)}/participants`);
  const { participants } = await res.json();

  const allowed = new Set(rule.allowedParticipants || []);
  const requireMention = rule.requireMention !== false;
  const allowMedia = rule.allowMedia !== false;
  const requireApproval = rule.requireApproval !== false;

  let html = `
    <div style="display:flex;align-items:center;gap:8px;margin-bottom:10px">
      <button class="btn btn-sm btn-outline" style="font-size:0.75em;padding:3px 8px" onclick="loadWAGroups()">${icon('arrow-left', 12)}</button>
      <span style="font-weight:600;font-size:0.9em">${escapeHtml(groupName)}</span>
      <span style="font-size:0.75em;color:var(--fg2)">${t('wa.groups.participants', { count: participants.length })}</span>
    </div>

    <div style="display:flex;flex-direction:column;gap:6px;margin-bottom:12px;padding:10px;background:var(--bg3);border-radius:6px">
      <label style="display:flex;align-items:center;gap:6px;font-size:0.82em;cursor:pointer">
        <input type="checkbox" id="wa-require-mention" ${requireMention ? 'checked' : ''}>
        <span>${t('wa.groups.mention.required')}</span>
      </label>
      <label style="display:flex;align-items:center;gap:6px;font-size:0.82em;cursor:pointer">
        <input type="checkbox" id="wa-allow-media" ${allowMedia ? 'checked' : ''}>
        <span>${icon('paperclip', 12)} ${t('wa.groups.media')}</span>
      </label>
      <label style="display:flex;align-items:center;gap:6px;font-size:0.82em;cursor:pointer">
        <input type="checkbox" id="wa-require-approval" ${requireApproval ? 'checked' : ''}>
        <span>${icon('lock', 12)} ${t('wa.groups.approval')}</span>
      </label>
    </div>

    <div style="margin-bottom:8px">
      <div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
        <span style="font-weight:500;font-size:0.85em">${icon('users', 14)} ${t('wa.groups.allowed.contacts')}</span>
        <span style="flex:1"></span>
        <button class="btn btn-sm btn-outline" style="font-size:0.7em;padding:2px 6px" onclick="waSelectAll(true)">${t('wa.groups.select.all')}</button>
        <button class="btn btn-sm btn-outline" style="font-size:0.7em;padding:2px 6px" onclick="waSelectAll(false)">${t('wa.groups.select.none')}</button>
      </div>
      <div style="font-size:0.72em;color:var(--fg2);margin-bottom:6px">${t('wa.groups.no.selection')}</div>
      <div id="wa-participants" style="max-height:250px;overflow-y:auto">`;

  for (const p of participants) {
    const checked = allowed.has(p.id) || allowed.has(p.number) ? 'checked' : '';
    const adminBadge = p.isAdmin ? ` <span style="background:var(--accent);color:var(--bg);padding:0 4px;border-radius:3px;font-size:0.7em">${t('wa.groups.admin')}</span>` : '';
    html += `<label style="display:flex;align-items:center;gap:6px;padding:4px 0;border-bottom:1px solid var(--glass-border);cursor:pointer;font-size:0.82em" class="wa-participant">
        <input type="checkbox" data-pid="${p.id}" data-number="${p.number}" ${checked}>
        <span style="flex:1">${escapeHtml(p.name)}${adminBadge}</span>
        <span style="color:var(--fg2);font-size:0.75em;font-family:monospace">+${p.number}</span>
      </label>`;
  }

  html += `</div></div>
    <div style="display:flex;gap:6px;margin-top:10px">
      <button class="btn btn-sm" onclick="saveWAGroupConfig('${groupId}', '${escapeHtml(groupName)}')">${icon('save', 12)} ${t('save')}</button>
      <button class="btn btn-sm btn-outline" onclick="loadWAGroups()">${t('cancel')}</button>
      ${rule.groupId ? `<button class="btn btn-sm btn-outline" style="color:var(--red);margin-left:auto" onclick="deleteWAGroupRule('${groupId}')">${icon('trash-2', 12)}</button>` : ''}
    </div>`;

  container.innerHTML = html;
}

function waSelectAll(selectAll) {
  document.querySelectorAll('#wa-participants input[type=checkbox]').forEach(cb => cb.checked = selectAll);
}

async function saveWAGroupConfig(groupId, groupName) {
  const requireMention = document.getElementById('wa-require-mention').checked;
  const allowMedia = document.getElementById('wa-allow-media').checked;
  const requireApproval = document.getElementById('wa-require-approval').checked;
  const allowedParticipants = [];
  const participantNames = {};
  document.querySelectorAll('#wa-participants input[type=checkbox]:checked').forEach(cb => {
    const pid = cb.dataset.pid;
    allowedParticipants.push(pid);
    const label = cb.closest('label');
    const name = label?.querySelector('span')?.textContent?.trim() || pid;
    participantNames[pid] = name;
  });

  await fetch(API + '/api/whatsapp/group-rules', {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ groupId, groupName, enabled: true, allowedParticipants, participantNames, requireMention, allowMedia, requireApproval }),
  });
  toast(t('wa.groups.configured', { name: groupName }));
  loadWAGroups();
}

async function deleteWAGroupRule(groupId) {
  if (!confirm(t('wa.groups.rule.delete.confirm'))) return;
  await fetch(API + `/api/whatsapp/group-rules/${encodeURIComponent(groupId)}`, { method: 'DELETE' });
  toast(t('wa.groups.rule.deleted'));
  loadWAGroups();
}

// ── Command Palette (Cmd+K) ─────────────────────────────
let _cmdPaletteIdx = 0;
let _cmdPaletteItems = [];

function openCommandPalette() {
  const overlay = document.getElementById('cmd-palette-overlay');
  overlay.style.display = 'flex';
  const input = document.getElementById('cmd-palette-input');
  input.value = '';
  input.focus();
  renderCommandPaletteResults('');
}

function closeCommandPalette() {
  document.getElementById('cmd-palette-overlay').style.display = 'none';
}

function getCommandPaletteItems() {
  const pages = [
    { id: 'chat', icon: 'message-square', label: t('nav.chat'), action: () => navigateTo('chat') },
    { id: 'dashboard', icon: 'layout-dashboard', label: t('nav.dashboard'), action: () => navigateTo('dashboard') },
    { id: 'models', icon: 'bot', label: t('nav.models'), action: () => navigateTo('models') },
    { id: 'personality', icon: 'palette', label: t('nav.personality'), action: () => navigateTo('personality') },
    { id: 'memory', icon: 'brain', label: t('nav.memory'), action: () => navigateTo('memory') },
    { id: 'sessions', icon: 'clipboard', label: t('nav.sessions'), action: () => navigateTo('sessions') },
    { id: 'files', icon: 'folder', label: t('nav.files'), action: () => navigateTo('files') },
    { id: 'cron', icon: 'timer', label: t('nav.cron'), action: () => navigateTo('cron') },
    { id: 'tools', icon: 'hammer', label: t('nav.tools'), action: () => navigateTo('tools') },
    { id: 'plugins', icon: 'plug', label: t('nav.plugins'), action: () => navigateTo('plugins') },
    { id: 'platforms', icon: 'smartphone', label: t('nav.platforms'), action: () => navigateTo('platforms') },
    { id: 'users', icon: 'users', label: t('nav.users'), action: () => navigateTo('users') },
    { id: 'terminal', icon: 'terminal', label: t('nav.terminal'), action: () => navigateTo('terminal') },
    { id: 'maintenance', icon: 'stethoscope', label: t('nav.maintenance'), action: () => navigateTo('maintenance') },
    { id: 'settings', icon: 'settings', label: t('nav.settings'), action: () => navigateTo('settings') },
  ];

  const actions = [
    { id: 'reset', icon: 'refresh-cw', label: t('cmd.action.reset'), kbd: navigator.platform?.includes('Mac') ? '⌘N' : 'Ctrl+N', action: () => resetChat() },
    { id: 'theme', icon: 'sun', label: t('cmd.action.theme'), action: () => toggleTheme() },
    { id: 'export', icon: 'download', label: t('cmd.action.export'), kbd: navigator.platform?.includes('Mac') ? '⌘⇧E' : 'Ctrl+Shift+E', action: () => exportChat() },
    { id: 'lang', icon: 'languages', label: t('cmd.action.lang'), action: () => toggleLang() },
  ];

  return { pages, actions };
}

function renderCommandPaletteResults(query) {
  const { pages, actions } = getCommandPaletteItems();
  const q = query.toLowerCase().trim();

  const filteredPages = q ? pages.filter(p => p.label.toLowerCase().includes(q) || p.id.includes(q)) : pages;
  const filteredActions = q ? actions.filter(a => a.label.toLowerCase().includes(q) || a.id.includes(q)) : actions;

  _cmdPaletteItems = [...filteredPages, ...filteredActions];
  _cmdPaletteIdx = 0;

  const container = document.getElementById('cmd-palette-results');

  if (_cmdPaletteItems.length === 0) {
    container.innerHTML = `<div class="cmd-palette-empty">${t('cmd.no.results')}</div>`;
    return;
  }

  let html = '';
  if (filteredPages.length > 0) {
    html += `<div class="cmd-palette-section">${t('cmd.goto')}</div>`;
    filteredPages.forEach((p, i) => {
      html += `<div class="cmd-palette-item${i === 0 ? ' selected' : ''}" data-idx="${i}" onclick="executeCmdPaletteItem(${i})" onmouseenter="selectCmdPaletteItem(${i})">
        ${icon(p.icon, 16)} <span>${p.label}</span>
      </div>`;
    });
  }
  if (filteredActions.length > 0) {
    html += `<div class="cmd-palette-section">${t('cmd.actions')}</div>`;
    filteredActions.forEach((a, j) => {
      const idx = filteredPages.length + j;
      html += `<div class="cmd-palette-item${idx === 0 && filteredPages.length === 0 ? ' selected' : ''}" data-idx="${idx}" onclick="executeCmdPaletteItem(${idx})" onmouseenter="selectCmdPaletteItem(${idx})">
        ${icon(a.icon, 16)} <span>${a.label}</span>
        ${a.kbd ? `<kbd>${a.kbd}</kbd>` : ''}
      </div>`;
    });
  }

  container.innerHTML = html;
}

function selectCmdPaletteItem(idx) {
  _cmdPaletteIdx = idx;
  document.querySelectorAll('.cmd-palette-item').forEach((el, i) => {
    el.classList.toggle('selected', i === idx);
  });
}

function executeCmdPaletteItem(idx) {
  const item = _cmdPaletteItems[idx];
  if (item) {
    closeCommandPalette();
    item.action();
  }
}

function navigateTo(page) {
  const navItem = document.querySelector(`.nav-item[data-page="${page}"]`);
  if (navItem) navItem.click();
}

// Command palette keyboard navigation
document.getElementById('cmd-palette-input').addEventListener('input', (e) => {
  renderCommandPaletteResults(e.target.value);
});

document.getElementById('cmd-palette-input').addEventListener('keydown', (e) => {
  if (e.key === 'Escape') { closeCommandPalette(); return; }
  if (e.key === 'ArrowDown') {
    e.preventDefault();
    selectCmdPaletteItem(Math.min(_cmdPaletteIdx + 1, _cmdPaletteItems.length - 1));
  }
  if (e.key === 'ArrowUp') {
    e.preventDefault();
    selectCmdPaletteItem(Math.max(_cmdPaletteIdx - 1, 0));
  }
  if (e.key === 'Enter') {
    e.preventDefault();
    executeCmdPaletteItem(_cmdPaletteIdx);
  }
});

// ── Theme Toggle ────────────────────────────────────────
function toggleTheme() {
  const body = document.documentElement;
  const current = body.getAttribute('data-theme');
  body.setAttribute('data-theme', current === 'light' ? '' : 'light');
  localStorage.setItem('theme', current === 'light' ? 'dark' : 'light');
}
if (localStorage.getItem('theme') === 'light') document.documentElement.setAttribute('data-theme', 'light');

// ── Setup Wizard ────────────────────────────────────────
let _wizardStep = 1;
let _wizardData = { botToken: '', allowedUsers: '', primaryProvider: '', apiKey: '', apiKeyEnv: '', webPassword: '', model: '', modelEnv: '', ollamaModel: '' };
let _wizardBotInfo = null;

// Maps provider family id → the env-var key that holds the chosen model
const FAMILY_MODEL_ENV_CLIENT = {
  groq: 'GROQ_MODEL',
  google: 'GOOGLE_MODEL',
  nvidia: 'NVIDIA_MODEL',
  anthropic: 'ANTHROPIC_MODEL',
  openai: 'OPENAI_MODEL',
  openrouter: 'OPENROUTER_MODEL',
};

let _wizardModelFetchTimer = null;
// What was already configured (e.g. via the terminal `setup`), so the wizard
// can skip re-asking for it instead of demanding everything from scratch.
let _wizardPreset = { tokenSet: false, usersSet: false, passwordSet: false, providerKeys: {} };

const WIZARD_PROVIDERS = [
  { id: 'groq', name: 'Groq', desc: 'Llama 3.3 70B — fast & free', envKey: 'GROQ_API_KEY', key: 'groq', free: true, recommended: true, signupUrl: 'https://console.groq.com/keys' },
  { id: 'google', name: 'Google Gemini', desc: 'Gemini 2.5 Flash — free tier', envKey: 'GOOGLE_API_KEY', key: 'gemini-2.5-flash', free: true, signupUrl: 'https://aistudio.google.com/apikey' },
  { id: 'nvidia', name: 'NVIDIA NIM', desc: 'Llama 3.3 70B — free tier', envKey: 'NVIDIA_API_KEY', key: 'nvidia-llama-3.3-70b', free: true, signupUrl: 'https://build.nvidia.com' },
  { id: 'anthropic', name: 'Anthropic', desc: 'Claude Opus / Sonnet / Haiku', envKey: 'ANTHROPIC_API_KEY', key: 'claude-opus', signupUrl: 'https://console.anthropic.com' },
  { id: 'openai', name: 'OpenAI', desc: 'GPT-4o, GPT-4.1, o3', envKey: 'OPENAI_API_KEY', key: 'gpt-4o', signupUrl: 'https://platform.openai.com/api-keys' },
  { id: 'openrouter', name: 'OpenRouter', desc: '200+ models, one API key', envKey: 'OPENROUTER_API_KEY', key: 'openrouter', signupUrl: 'https://openrouter.ai/keys' },
  { id: 'claude-sdk', name: 'Claude Agent SDK', desc: 'Full agent + tools (CLI login)', envKey: '', key: 'claude-sdk', premium: true },
  { id: 'ollama', name: 'Ollama (Local)', desc: 'Run models locally, no API key', envKey: '', key: 'ollama', free: true, signupUrl: 'https://ollama.com/download' },
];

async function checkSetupNeeded() {
  try {
    const res = await fetch(API + '/api/setup-check');
    const data = await res.json();
    if (!data.isComplete) { showWizard(data); return true; }
  } catch {}
  return false;
}

function showWizard(setupData) {
  document.getElementById('wizard-overlay').style.display = 'flex';
  const tg = setupData?.steps?.telegram || {};
  const pv = setupData?.steps?.provider || {};
  _wizardPreset = {
    tokenSet: !!tg.botToken,
    usersSet: !!tg.allowedUsers,
    passwordSet: !!(setupData?.steps?.web?.passwordSet),
    providerKeys: pv.keys || {},
  };
  // Pre-fill from existing config so already-configured fields aren't re-demanded.
  if (tg.botToken) _wizardData.botToken = '(already set)';
  if (tg.allowedUsers) _wizardData.allowedUsers = '(already set)';
  // Pre-select the provider already set in .env — don't make the user pick again.
  if (pv.primary) {
    const wp = WIZARD_PROVIDERS.find(p => p.key === pv.primary || p.id === pv.primary);
    if (wp) _wizardData.primaryProvider = wp.id;
  }
  _wizardStep = 1;
  if (tg.done) _wizardStep = 2;
  if (pv.done) _wizardStep = 3;
  renderWizardStep();
}

function renderWizardStep() {
  const header = document.getElementById('wizard-header');
  const body = document.getElementById('wizard-body');
  const footer = document.getElementById('wizard-footer');

  // Header with step dots
  header.innerHTML = `
    <h1>${icon('bot', 28)} ${t('wizard.title')}</h1>
    <div class="wizard-subtitle">${t('wizard.subtitle')}</div>
    <div class="wizard-steps">
      ${[1,2,3].map(n => `<div class="wizard-step-dot ${n === _wizardStep ? 'active' : n < _wizardStep ? 'done' : ''}"></div>`).join('')}
    </div>
    <div style="font-size:0.75em;color:var(--fg3);margin-top:8px">${t('wizard.step', { n: _wizardStep, total: 3 })}</div>
  `;

  if (_wizardStep === 1) renderWizardStep1(body, footer);
  else if (_wizardStep === 2) renderWizardStep2(body, footer);
  else if (_wizardStep === 3) renderWizardStep3(body, footer);
}

function renderWizardStep1(body, footer) {
  body.innerHTML = `
    <h2>${icon('send', 20)} ${t('wizard.step1.title')}</h2>
    <div class="wizard-desc">${t('wizard.step1.desc')}</div>
    <div style="background:var(--bg3);border-radius:var(--radius-sm);padding:14px;margin-bottom:16px;font-size:0.85em;line-height:1.8">
      ${t('wizard.step1.instruction1')} <a href="https://t.me/BotFather" target="_blank" style="color:var(--accent2);font-weight:600">@BotFather</a><br>
      ${t('wizard.step1.instruction2')}<br>
      ${t('wizard.step1.instruction3')}
    </div>
    <div class="wizard-field">
      <label>Bot Token</label>
      <div style="display:flex;gap:8px">
        <input id="wiz-token" type="text" placeholder="${_wizardPreset.tokenSet ? t('wizard.alreadySet') : t('wizard.step1.token.placeholder')}" value="${_wizardData.botToken.startsWith('(') ? '' : _wizardData.botToken}" style="flex:1;font-family:monospace">
        <button class="btn btn-sm" onclick="validateWizardToken()">${icon('check', 14)} ${t('wizard.step1.token.validate')}</button>
      </div>
      <div id="wiz-token-result">${_wizardPreset.tokenSet ? `<div class="wizard-hint">${icon('check', 13)} ${t('wizard.keepHint')}</div>` : ''}</div>
    </div>
    <div class="wizard-field">
      <label>${t('wizard.step1.userid')}${_wizardPreset.usersSet ? '' : ' *'}</label>
      <input id="wiz-userid" type="text" placeholder="${_wizardPreset.usersSet ? t('wizard.alreadySet') : t('wizard.step1.userid.placeholder')}" value="${_wizardData.allowedUsers.startsWith('(') ? '' : _wizardData.allowedUsers}" style="font-family:monospace">
      <div class="wizard-hint">${_wizardPreset.usersSet ? `${icon('check', 13)} ${t('wizard.keepHint')}` : `${t('wizard.step1.userid.help')} <a href="https://t.me/userinfobot" target="_blank">@userinfobot</a>`}</div>
    </div>
  `;
  footer.innerHTML = `
    <button class="btn btn-outline" onclick="document.getElementById('wizard-overlay').style.display='none'">${t('wizard.skip')}</button>
    <button class="btn" onclick="wizardNext()" id="wiz-next-btn">${icon('chevron-right', 16)} ${t('wizard.next')}</button>
  `;
}

async function validateWizardToken() {
  const token = document.getElementById('wiz-token').value.trim();
  const el = document.getElementById('wiz-token-result');
  if (!token) { el.innerHTML = ''; return; }
  el.innerHTML = `<div class="wizard-validation" style="color:var(--fg2)">${icon('refresh-cw', 14)} ...</div>`;
  try {
    const res = await fetch(API + '/api/validate-bot-token', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ token }),
    });
    const data = await res.json();
    if (data.ok) {
      _wizardBotInfo = data.bot;
      _wizardData.botToken = token;
      el.innerHTML = `<div class="wizard-validation success">${icon('check', 14)} ${t('wizard.step1.token.valid')} <strong>@${data.bot.username}</strong> (${data.bot.firstName})</div>`;
    } else {
      el.innerHTML = `<div class="wizard-validation error">${icon('x', 14)} ${t('wizard.step1.token.invalid')}: ${data.error}</div>`;
    }
  } catch (e) {
    el.innerHTML = `<div class="wizard-validation error">${icon('alert-triangle', 14)} ${e.message}</div>`;
  }
}

function renderWizardStep2(body, footer) {
  const selected = _wizardData.primaryProvider;
  body.innerHTML = `
    <h2>${icon('bot', 20)} ${t('wizard.step2.title')}</h2>
    <div class="wizard-desc">${t('wizard.step2.desc')}</div>
    <div class="wizard-provider-grid">
      ${WIZARD_PROVIDERS.map(p => `
        <div class="wizard-provider-card ${selected === p.id ? 'selected' : ''}" onclick="selectWizardProvider('${p.id}')" data-provider="${p.id}">
          <div class="provider-name">${p.name}</div>
          <div class="provider-desc">${p.desc}</div>
          ${p.free ? `<span class="provider-badge free">${t('wizard.step2.free')}</span>` : ''}
          ${p.recommended ? `<span class="provider-badge recommended">${t('wizard.step2.recommended')}</span>` : ''}
          ${p.premium ? `<span class="provider-badge" style="background:rgba(253,203,110,0.15);color:var(--yellow)">${t('wizard.step2.premium')}</span>` : ''}
        </div>
      `).join('')}
    </div>
    <div id="wiz-provider-config" style="display:${selected ? 'block' : 'none'}"></div>
  `;
  if (selected) renderWizardProviderConfig(selected);
  footer.innerHTML = `
    <button class="btn btn-outline" onclick="_wizardStep=1;renderWizardStep()">${icon('arrow-left', 14)} ${t('wizard.back')}</button>
    <button class="btn" onclick="wizardNext()">${icon('chevron-right', 16)} ${t('wizard.next')}</button>
  `;
}

function selectWizardProvider(id) {
  _wizardData.primaryProvider = id;
  document.querySelectorAll('.wizard-provider-card').forEach(c => c.classList.toggle('selected', c.dataset.provider === id));
  const provider = WIZARD_PROVIDERS.find(p => p.id === id);
  const cfg = document.getElementById('wiz-provider-config');
  cfg.style.display = 'block';
  renderWizardProviderConfig(id);
}

function renderWizardProviderConfig(id) {
  // Reset any model selection carried over from a previous provider, and
  // cancel a pending debounced fetch so a stale provider's models can't
  // overwrite the new selection.
  _wizardData.model = '';
  _wizardData.modelEnv = '';
  _wizardData.ollamaModel = '';
  if (_wizardModelFetchTimer) { clearTimeout(_wizardModelFetchTimer); _wizardModelFetchTimer = null; }

  const provider = WIZARD_PROVIDERS.find(p => p.id === id);
  const cfg = document.getElementById('wiz-provider-config');
  if (!provider) { cfg.innerHTML = ''; return; }

  if (provider.id === 'claude-sdk') {
    cfg.innerHTML = `<div style="background:var(--bg3);border-radius:var(--radius-sm);padding:14px;font-size:0.85em;line-height:1.8">
      <strong>Claude Agent SDK</strong> — requires Claude Max subscription ($20/month)<br>
      1. <code style="background:rgba(0,0,0,0.2);padding:2px 6px;border-radius:4px">npm install -g @anthropic-ai/claude-code</code><br>
      2. <code style="background:rgba(0,0,0,0.2);padding:2px 6px;border-radius:4px">claude login</code><br>
      No API key needed — uses CLI auth.
    </div>`;
    _wizardData.apiKeyEnv = '';
    _wizardData.apiKey = '';
    return;
  }
  if (provider.id === 'ollama') {
    cfg.innerHTML = `<div style="background:var(--bg3);border-radius:var(--radius-sm);padding:14px;font-size:0.85em;line-height:1.8">
      <strong>Ollama</strong> — runs locally on your machine<br>
      Install: <a href="https://ollama.com/download" target="_blank" style="color:var(--accent2)">ollama.com/download</a>
    </div>
    <div id="wiz-offline-picker" style="margin-top:12px">
      <div style="color:var(--fg2);font-size:0.85em">${icon('refresh-cw', 12)} Loading available models…</div>
    </div>`;
    _wizardData.apiKeyEnv = '';
    _wizardData.apiKey = '';
    _loadWizardOfflinePicker();
    return;
  }

  const keySet = !!_wizardPreset.providerKeys?.[provider.id];

  // anthropic routes through claude-sdk at runtime (ClaudeSDKProvider), so
  // ANTHROPIC_MODEL written by a cloud picker would be inert. Only show the
  // model picker for families whose <FAMILY>_MODEL env var actually takes effect.
  const showModelPicker = provider.id !== 'anthropic';

  cfg.innerHTML = `
    <div class="wizard-field">
      <label>${provider.name} API Key</label>
      <input id="wiz-apikey" type="password" placeholder="${keySet ? t('wizard.alreadySet') : t('wizard.step2.key.placeholder')}" value="${_wizardData.apiKey}" style="font-family:monospace">
      <div class="wizard-hint">
        ${keySet ? `${icon('check', 13)} ${t('wizard.keepHint')}` : `${provider.signupUrl ? `<a href="${provider.signupUrl}" target="_blank">${provider.signupUrl}</a>` : ''}${provider.free ? ` — ${t('wizard.step2.free')}!` : ''}`}
      </div>
    </div>
    ${showModelPicker ? `<div id="wiz-model-picker" style="margin-top:8px">
      <div class="wizard-field">
        <label>Model <span style="color:var(--fg3);font-weight:400;font-size:0.85em">(optional — leave blank to use default)</span></label>
        <select id="wiz-model-select" disabled style="background:var(--bg3);border:1px solid var(--glass-border);border-radius:6px;padding:8px 12px;color:var(--fg);font:inherit;font-size:0.85em;width:100%">
          <option value="">— enter API key above to load models —</option>
        </select>
        <div id="wiz-model-hint" class="wizard-hint" style="display:none"></div>
      </div>
    </div>` : ''}
  `;
  _wizardData.apiKeyEnv = provider.envKey;

  // Wire up key input → debounced model fetch (skip for anthropic — picker not shown)
  if (showModelPicker) {
    const keyInput = document.getElementById('wiz-apikey');
    if (keyInput) {
      const triggerFetch = () => {
        if (_wizardModelFetchTimer) clearTimeout(_wizardModelFetchTimer);
        _wizardModelFetchTimer = setTimeout(() => _loadWizardCloudModels(provider.id), 600);
      };
      keyInput.addEventListener('blur', triggerFetch);
      keyInput.addEventListener('input', triggerFetch);
      // If we already have an api key value stored, kick off a fetch immediately
      if (_wizardData.apiKey || keySet) {
        _wizardModelFetchTimer = setTimeout(() => _loadWizardCloudModels(provider.id), 100);
      }
    }
  }
}

async function _loadWizardCloudModels(providerId) {
  const sel = document.getElementById('wiz-model-select');
  const hint = document.getElementById('wiz-model-hint');
  if (!sel) return;

  sel.disabled = true;
  sel.innerHTML = `<option value="">Loading models…</option>`;

  try {
    const res = await fetch(API + '/api/providers/live-models?id=' + providerId);
    const data = await res.json();

    if (!data.ok || !data.models?.length) {
      sel.innerHTML = `<option value="">— no models found (check API key) —</option>`;
      if (hint) { hint.style.display = 'block'; hint.textContent = 'Enter or save your API key first, then models will appear here.'; }
      return;
    }

    sel.innerHTML = `<option value="">— pick a model (optional) —</option>`;
    for (const m of data.models) {
      const opt = document.createElement('option');
      opt.value = m.id;
      opt.textContent = m.id + (m.recommended ? ' ★ recommended' : '');
      if (m.id === data.recommendedId) opt.selected = true;
      sel.appendChild(opt);
    }
    sel.disabled = false;

    // Pre-select and store if there's a recommendedId
    if (data.recommendedId) {
      sel.value = data.recommendedId;
      _wizardData.model = data.recommendedId;
      _wizardData.modelEnv = FAMILY_MODEL_ENV_CLIENT[providerId] || '';
    }
    if (hint) hint.style.display = 'none';

    sel.onchange = () => {
      _wizardData.model = sel.value || '';
      _wizardData.modelEnv = sel.value ? (FAMILY_MODEL_ENV_CLIENT[providerId] || '') : '';
    };
  } catch (err) {
    sel.innerHTML = `<option value="">— error loading models —</option>`;
    if (hint) { hint.style.display = 'block'; hint.textContent = err.message; }
  }
}

async function _loadWizardOfflinePicker() {
  const container = document.getElementById('wiz-offline-picker');
  if (!container) return;

  try {
    const res = await fetch(API + '/api/offline/recommend');
    const data = await res.json();

    if (!data.models?.length) {
      container.innerHTML = `<div style="color:var(--fg2);font-size:0.85em">No local models found. Install Ollama and pull a model first.</div>`;
      return;
    }

    const fitIcon = fit => fit === 'fits' ? '✓' : fit === 'tight' ? '⚠' : '✗';
    const fitColor = fit => fit === 'fits' ? 'var(--green)' : fit === 'tight' ? 'var(--yellow)' : 'var(--red)';

    let html = `<div class="wizard-field">
      <label>Local model <span style="color:var(--fg3);font-weight:400;font-size:0.85em">(your machine has ~${data.totalGB} GB RAM)</span></label>
      <select id="wiz-ollama-select" style="background:var(--bg3);border:1px solid var(--glass-border);border-radius:6px;padding:8px 12px;color:var(--fg);font:inherit;font-size:0.85em;width:100%">`;

    for (const m of data.models) {
      const label = `${escapeHtml(m.label)} (~${m.sizeGB} GB)${m.tag === data.defaultTag ? ' ★ recommended' : ''} ${fitIcon(m.fit)}`;
      html += `<option value="${escapeHtml(m.tag)}" ${m.tag === data.defaultTag ? 'selected' : ''}>${label}</option>`;
    }
    html += `</select>`;

    // Show fit legend for the currently selected model
    const defaultModel = data.models.find(m => m.tag === data.defaultTag) || data.models[0];
    html += `<div id="wiz-ollama-fit-hint" class="wizard-hint" style="color:${fitColor(defaultModel?.fit || 'fits')}">
      ${defaultModel ? `${fitIcon(defaultModel.fit)} ${defaultModel.fit === 'fits' ? 'Fits comfortably in your RAM' : defaultModel.fit === 'tight' ? 'May be slow — close other apps' : 'Too large for your RAM — may crash'}` : ''}
    </div>`;
    html += `</div>`;

    container.innerHTML = html;

    // Pre-store default selection
    _wizardData.ollamaModel = data.defaultTag || data.models[0]?.tag || '';

    const sel = document.getElementById('wiz-ollama-select');
    if (sel) {
      sel.onchange = () => {
        _wizardData.ollamaModel = sel.value;
        const chosen = data.models.find(m => m.tag === sel.value);
        const fitHint = document.getElementById('wiz-ollama-fit-hint');
        if (fitHint && chosen) {
          fitHint.style.color = fitColor(chosen.fit);
          fitHint.textContent = `${fitIcon(chosen.fit)} ${chosen.fit === 'fits' ? 'Fits comfortably in your RAM' : chosen.fit === 'tight' ? 'May be slow — close other apps' : 'Too large for your RAM — may crash'}`;
        }
      };
    }
  } catch (err) {
    container.innerHTML = `<div style="color:var(--fg2);font-size:0.85em">Could not load model list: ${escapeHtml(err.message || 'error')}</div>`;
  }
}

function renderWizardStep3(body, footer) {
  body.innerHTML = `
    <h2>${icon('check', 20)} ${t('wizard.step3.title')}</h2>
    <div class="wizard-desc">${t('wizard.step3.desc')}</div>
    <div class="wizard-field">
      <label>${t('wizard.step3.password')}</label>
      <input id="wiz-password" type="password" placeholder="${_wizardPreset.passwordSet ? t('wizard.alreadySet') : t('wizard.step3.password.placeholder')}" value="${_wizardData.webPassword}">
      <div class="wizard-hint">${_wizardPreset.passwordSet ? `${icon('check', 13)} ${t('wizard.keepHint')}` : t('wizard.step3.password.help')}</div>
    </div>
    <div style="background:var(--bg3);border-radius:var(--radius-sm);padding:16px;margin-top:16px">
      <h3 style="font-size:0.85em;margin-bottom:10px;display:flex;align-items:center;gap:6px">${icon('list', 16)} Summary</h3>
      <div style="font-size:0.82em;color:var(--fg2);display:flex;flex-direction:column;gap:6px">
        <div style="display:flex;gap:8px">${icon('send', 14)} <strong>Telegram:</strong> ${_wizardBotInfo ? `@${_wizardBotInfo.username}` : (_wizardData.botToken ? 'Token set' : 'Not configured')}</div>
        <div style="display:flex;gap:8px">${icon('user', 14)} <strong>User ID:</strong> ${_wizardData.allowedUsers || 'Not set'}</div>
        <div style="display:flex;gap:8px">${icon('bot', 14)} <strong>Provider:</strong> ${WIZARD_PROVIDERS.find(p=>p.id===_wizardData.primaryProvider)?.name || 'Not selected'}</div>
      </div>
    </div>
  `;
  footer.innerHTML = `
    <button class="btn btn-outline" onclick="_wizardStep=2;renderWizardStep()">${icon('arrow-left', 14)} ${t('wizard.back')}</button>
    <button class="btn btn-success" onclick="finishWizard()" style="min-width:160px">${icon('check', 16)} ${t('wizard.finish')}</button>
  `;
}

function wizardNext() {
  if (_wizardStep === 1) {
    const token = document.getElementById('wiz-token')?.value?.trim();
    const userId = document.getElementById('wiz-userid')?.value?.trim();
    if (token) _wizardData.botToken = token;
    if (userId) _wizardData.allowedUsers = userId;
    // '(already set)' means it's configured already (e.g. via terminal setup) —
    // only a genuinely empty field should block progress.
    if (!_wizardData.botToken) {
      toast(t('wizard.step1.token.invalid'), 'error'); return;
    }
    if (!_wizardData.allowedUsers) {
      toast(t('wizard.step1.userid') + ' required', 'error'); return;
    }
  }
  if (_wizardStep === 2) {
    if (!_wizardData.primaryProvider) { toast('Please select a provider', 'error'); return; }
    const provider = WIZARD_PROVIDERS.find(p => p.id === _wizardData.primaryProvider);
    const keyInput = document.getElementById('wiz-apikey');
    if (keyInput) _wizardData.apiKey = keyInput.value.trim();
    // Don't re-demand an API key that's already in .env for this provider.
    const keyAlreadySet = !!_wizardPreset.providerKeys?.[_wizardData.primaryProvider];
    if (provider?.envKey && !_wizardData.apiKey && !keyAlreadySet) { toast('API key required', 'error'); return; }
  }
  _wizardStep++;
  renderWizardStep();
}

async function finishWizard() {
  const pw = document.getElementById('wiz-password')?.value?.trim();
  if (pw) _wizardData.webPassword = pw;

  const provider = WIZARD_PROVIDERS.find(p => p.id === _wizardData.primaryProvider);
  const payload = {
    botToken: _wizardData.botToken.startsWith('(') ? undefined : _wizardData.botToken,
    allowedUsers: _wizardData.allowedUsers.startsWith('(') ? undefined : _wizardData.allowedUsers,
    primaryProvider: provider?.key || _wizardData.primaryProvider,
    apiKey: _wizardData.apiKey || undefined,
    apiKeyEnv: _wizardData.apiKeyEnv || undefined,
    webPassword: _wizardData.webPassword || undefined,
    model: _wizardData.model || undefined,
    modelEnv: _wizardData.modelEnv || undefined,
    ollamaModel: _wizardData.ollamaModel || undefined,
  };

  try {
    const res = await fetch(API + '/api/setup-wizard', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    const data = await res.json();
    if (data.ok) {
      document.getElementById('wizard-overlay').style.display = 'none';
      toast(t('wizard.finish.success'), 'success');

      // Show restart notice (and optional pull progress for offline)
      const overlay = document.createElement('div');
      overlay.style.cssText = 'position:fixed;inset:0;background:var(--bg);z-index:99999;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:16px;padding:24px';
      overlay.innerHTML = `
        <div style="font-size:1.2em;font-weight:600;display:flex;align-items:center;gap:10px">${icon('check', 24)} ${t('wizard.finish.success')}</div>
        <div style="color:var(--fg2);font-size:0.9em">${t('wizard.finish.restart')}</div>
        <div style="color:var(--fg3);font-size:0.82em;margin-top:8px">Or restart from the Maintenance page after refresh.</div>
        <div id="wiz-pull-area" style="margin-top:12px;width:100%;max-width:400px"></div>
      `;
      document.body.appendChild(overlay);

      // If ollama model selected, kick off pull (non-blocking)
      if (_wizardData.ollamaModel) {
        _startWizardPull(_wizardData.ollamaModel);
      }
    } else {
      toast(data.error || 'Setup failed', 'error');
    }
  } catch (e) {
    toast(e.message, 'error');
  }
}

async function _startWizardPull(tag) {
  const area = document.getElementById('wiz-pull-area');
  if (!area) return;

  const installLink = `<a href="https://ollama.com/download" target="_blank" style="color:var(--accent2)">ollama.com/download</a>`;

  area.innerHTML = `<div style="font-size:0.85em;color:var(--fg2)">${icon('refresh-cw', 12)} Starting download of <strong>${tag}</strong>…</div>`;

  try {
    const res = await fetch(API + '/api/offline/pull', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ tag }),
    });
    const data = await res.json();

    if (data.reason === 'binary-missing') {
      area.innerHTML = `<div style="font-size:0.85em;background:var(--bg3);border-radius:6px;padding:10px">
        ${icon('alert-triangle', 14)} Ollama is not installed yet.<br>
        Install it first: ${installLink}, then re-open setup to pull the model.
      </div>`;
      return;
    }
    if (data.reason === 'busy') {
      area.innerHTML = `<div style="font-size:0.85em;color:var(--fg2)">${icon('refresh-cw', 12)} A pull is already running — monitoring progress…</div>`;
      // Fall through to polling
    }
  } catch (err) {
    area.innerHTML = `<div style="font-size:0.85em;color:var(--red)">${icon('x', 12)} Could not start pull: ${err.message}</div>`;
    return;
  }

  // Poll pull-status
  let pollTimer;
  const renderProgress = (st) => {
    const pct = st.percent != null ? Math.round(st.percent) : null;
    const barHtml = pct != null ? `
      <div style="margin-top:6px;background:var(--bg3);border-radius:4px;height:8px;overflow:hidden">
        <div style="background:var(--accent);height:100%;width:${pct}%;transition:width 0.3s"></div>
      </div>
      <div style="font-size:0.78em;color:var(--fg2);margin-top:3px">${pct}%</div>` : '';
    if (st.state === 'done') {
      area.innerHTML = `<div style="font-size:0.85em;color:var(--green)">${icon('check', 14)} Model <strong>${tag}</strong> downloaded and ready.</div>`;
    } else if (st.state === 'error') {
      area.innerHTML = `<div style="font-size:0.85em;background:var(--bg3);border-radius:6px;padding:10px">
        ${icon('x', 14)} Pull failed: ${escapeHtml(st.error || 'unknown error')}<br>
        If Ollama is not installed: ${installLink}
      </div>`;
    } else {
      area.innerHTML = `<div style="font-size:0.85em">
        <div style="color:var(--fg2)">${icon('refresh-cw', 12)} Pulling <strong>${tag}</strong>…</div>
        ${barHtml}
      </div>`;
    }
  };

  area.innerHTML = `<div style="font-size:0.85em;color:var(--fg2)">${icon('refresh-cw', 12)} Pulling <strong>${tag}</strong>…</div>`;

  let pollCount = 0;
  const MAX_POLLS = 200;
  pollTimer = setInterval(async () => {
    if (++pollCount > MAX_POLLS) {
      clearInterval(pollTimer);
      area.innerHTML = `<div style="font-size:0.85em;color:var(--fg2)">Download is taking a long time — check Ollama, then refresh.</div>`;
      return;
    }
    try {
      const r = await fetch(API + '/api/offline/pull-status');
      const st = await r.json();
      renderProgress(st);
      if (st.state === 'done' || st.state === 'error') clearInterval(pollTimer);
    } catch {
      // Swallow polling errors — overlay can be closed by user anyway
    }
  }, 1500);
}

// ── Reliability (v5.19.0) ───────────────────────────────
// Panel for the v5.18.0 reliability surface area: restart-storm counter,
// state-file health snapshot, recent incident dumps, EXEC_SECURITY mode,
// TTS provider + Supertonic status with a live-switch dropdown.
let reliabilityPollTimer = null;
async function loadReliability() {
  const root = document.getElementById('reliability-content');
  if (!root) return;
  if (reliabilityPollTimer) { clearInterval(reliabilityPollTimer); reliabilityPollTimer = null; }
  await renderReliability();
  // Soft poll every 8 s while the page is visible — stops when the user
  // navigates away (loadX() of another page clears nothing, but we rely on
  // the timer firing into a hidden DOM as a no-op).
  reliabilityPollTimer = setInterval(() => {
    const visible = document.getElementById('page-reliability')?.classList.contains('active');
    if (visible) renderReliability();
  }, 8000);
}

async function renderReliability() {
  const root = document.getElementById('reliability-content');
  if (!root) return;
  let rel, tts;
  try {
    [rel, tts] = await Promise.all([
      fetch(API + '/api/reliability').then(r => r.json()),
      fetch(API + '/api/tts').then(r => r.json()),
    ]);
  } catch (err) {
    root.innerHTML = `<div class="card"><div style="color:var(--red)">${t('error')}: ${escapeHtml(err.message || String(err))}</div></div>`;
    return;
  }

  // Restart-Storm card
  const storm = rel.restartStorm || {};
  const attempts = (storm.attempts && Array.isArray(storm.attempts) ? storm.attempts : []).length;
  const threshold = storm.threshold ?? 3;
  const windowMin = Math.round((storm.windowMs ?? 600000) / 60000);
  const stormColor = attempts === 0 ? 'green' : (attempts < threshold ? 'yellow' : 'red');
  const lastBrakeStr = storm.lastBrakeAt ? new Date(storm.lastBrakeAt).toLocaleString() : '—';

  // Subsystems card (v5.46.0) — live composite-beacon heartbeats
  const subs = rel.subsystems || {};
  const subsAlive = subs.alive || {};
  const subsThresholds = subs.thresholdsMs || {};
  const subsCritical = Array.isArray(subs.critical) ? subs.critical : [];
  const subsNow = subs.now || Date.now();
  const verdictState = (subs.verdict && subs.verdict.state) || 'healthy';
  const verdictColor = verdictState === 'healthy' ? 'green' : (verdictState === 'degraded' ? 'yellow' : 'red');
  const subsystemRows = Object.entries(subsThresholds).map(([name, thresholdMs]) => {
    const last = subsAlive[name];
    const ageS = typeof last === 'number' ? Math.round((subsNow - last) / 1000) : null;
    const stale = ageS === null || (subsNow - (last || 0)) > thresholdMs;
    const color = !stale ? 'green' : (subsCritical.includes(name) ? 'red' : 'yellow');
    const ageStr = ageS === null
      ? t('reliability.subsystems.never')
      : t('reliability.subsystems.ago').replace('{s}', String(ageS));
    const critBadge = subsCritical.includes(name)
      ? ` <span style="background:var(--bg3);padding:1px 6px;border-radius:4px;font-size:0.72em">critical</span>` : '';
    return `<tr>
      <td style="font-family:monospace;font-size:0.85em">${escapeHtml(name)}${critBadge}</td>
      <td>${icon(stale ? 'circle-alert' : 'circle-check', 14, `style="color:var(--${color})"`)}</td>
      <td style="color:var(--fg3);font-size:0.82em;text-align:right">${escapeHtml(ageStr)}</td>
    </tr>`;
  }).join('');

  // State-files
  const stateFiles = Array.isArray(rel.stateFiles) ? rel.stateFiles : [];
  const stateRows = stateFiles.map(f => {
    const filename = (f.file || '').split('/').pop();
    const statusColor = f.status === 'ok' || f.status === 'missing' ? 'green' : (f.status === 'repaired' ? 'yellow' : 'red');
    const statusIcon = f.status === 'ok' ? 'circle-check' : (f.status === 'missing' ? 'circle' : (f.status === 'repaired' ? 'circle-alert' : 'circle-x'));
    return `<tr>
      <td style="font-family:monospace;font-size:0.85em">${escapeHtml(filename)}</td>
      <td>${icon(statusIcon, 14, `style="color:var(--${statusColor})"`)} ${escapeHtml(f.status)}</td>
      <td style="color:var(--fg3);font-size:0.82em">${escapeHtml(f.reason || '')}</td>
    </tr>`;
  }).join('');

  // Incidents
  const incidents = Array.isArray(rel.recentIncidents) ? rel.recentIncidents : [];
  const incidentRows = incidents.length === 0
    ? `<tr><td colspan="2" style="color:var(--fg3);text-align:center;padding:12px">${t('reliability.incidents.none')}</td></tr>`
    : incidents.map(name => {
        // Filenames look like "2026-05-24T08-05-51-647Z-system-reminder-leak.json"
        const m = name.match(/^(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})/);
        const when = m ? m[1].replace('T', ' ').replace(/-/g, (c, i) => i < 10 ? '-' : ':') : name;
        const kind = name.replace(/^[\d\-T]+-/, '').replace(/\.json$/, '');
        return `<tr>
          <td style="font-family:monospace;font-size:0.82em">${escapeHtml(when)}</td>
          <td><span style="background:var(--bg3);padding:2px 8px;border-radius:4px;font-size:0.82em">${escapeHtml(kind)}</span></td>
        </tr>`;
      }).join('');

  // TTS section
  const ttsProvider = tts.provider || 'auto';
  const supertonic = tts.supertonic || {};
  const supertonicReady = supertonic.ready === true;
  const supertonicBadge = supertonicReady
    ? `<span style="color:var(--green)">${icon('circle-check', 12)} ready</span>`
    : `<span style="color:var(--yellow)">${icon('circle-alert', 12)} ${escapeHtml(supertonic.blocker || 'not installed')}</span>`;

  const execSec = rel.execSecurity || 'allowlist';
  const execColor = execSec === 'full' ? 'yellow' : (execSec === 'deny' ? 'red' : 'green');

  root.innerHTML = `
    <div class="cards" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,360px),1fr));gap:14px;align-items:start">
      <div class="card">
        <h3 style="font-size:0.9em;text-transform:none;letter-spacing:0;margin-bottom:8px;display:flex;align-items:center;gap:8px">
          ${icon('refresh-cw', 16)} ${t('reliability.restart.title')}
        </h3>
        <div style="font-size:0.85em;margin-bottom:8px">
          <div style="display:flex;justify-content:space-between"><span>${t('reliability.restart.attempts')}</span><strong style="color:var(--${stormColor})">${attempts} / ${threshold}</strong></div>
          <div style="display:flex;justify-content:space-between"><span>${t('reliability.restart.window')}</span><span>${windowMin} min</span></div>
          <div style="display:flex;justify-content:space-between"><span>${t('reliability.restart.lastBrake')}</span><span style="font-size:0.82em">${escapeHtml(lastBrakeStr)}</span></div>
        </div>
        <div class="sub" style="font-size:0.78em">${t('reliability.restart.help')}</div>
      </div>

      <div class="card">
        <h3 style="font-size:0.9em;text-transform:none;letter-spacing:0;margin-bottom:8px;display:flex;align-items:center;gap:8px">
          ${icon('activity', 16)} ${t('reliability.subsystems.title')}
          <span style="margin-left:auto;color:var(--${verdictColor});font-size:0.82em;font-weight:600">${escapeHtml(verdictState)}</span>
        </h3>
        <table style="width:100%;font-size:0.85em;border-collapse:collapse">
          <tbody>${subsystemRows || `<tr><td colspan="3" style="color:var(--fg3);text-align:center;padding:8px">${t('no.data')}</td></tr>`}</tbody>
        </table>
        <div class="sub" style="font-size:0.78em;margin-top:8px">${t('reliability.subsystems.help')}</div>
      </div>

      <div class="card">
        <h3 style="font-size:0.9em;text-transform:none;letter-spacing:0;margin-bottom:8px;display:flex;align-items:center;gap:8px">
          ${icon('database', 16)} ${t('reliability.state.title')}
        </h3>
        <table style="width:100%;font-size:0.85em;border-collapse:collapse">
          <tbody>${stateRows || `<tr><td colspan="3" style="color:var(--fg3);text-align:center;padding:8px">${t('no.data')}</td></tr>`}</tbody>
        </table>
        <div class="sub" style="font-size:0.78em;margin-top:8px">${t('reliability.state.help')}</div>
      </div>

      <div class="card">
        <h3 style="font-size:0.9em;text-transform:none;letter-spacing:0;margin-bottom:8px;display:flex;align-items:center;gap:8px">
          ${icon('shield', 16)} ${t('reliability.exec.title')}
        </h3>
        <div style="font-size:0.95em;font-weight:600;color:var(--${execColor})">${escapeHtml(execSec)}</div>
        <div class="sub" style="font-size:0.78em;margin-top:6px">${t('reliability.exec.help.' + execSec)}</div>
      </div>

      <div class="card">
        <h3 style="font-size:0.9em;text-transform:none;letter-spacing:0;margin-bottom:8px;display:flex;align-items:center;gap:8px">
          ${icon('volume-2', 16)} ${t('reliability.tts.title')}
        </h3>
        <div style="font-size:0.85em;margin-bottom:8px">
          <div style="display:flex;justify-content:space-between;align-items:center">
            <span>${t('reliability.tts.current')}</span>
            <span style="font-family:monospace;font-weight:600">${escapeHtml(ttsProvider)}</span>
          </div>
          <div style="display:flex;justify-content:space-between;margin-top:4px;align-items:center">
            <span>${t('reliability.tts.supertonic')}</span>${supertonicBadge}
          </div>
        </div>
        <div style="display:flex;gap:6px;margin-top:8px;flex-wrap:wrap">
          <select id="tts-provider-select" style="flex:1;background:var(--bg3);border:1px solid var(--border);border-radius:6px;padding:6px 8px;color:var(--fg);font-size:0.85em">
            <option value="supertonic" ${ttsProvider==='supertonic'?'selected':''} ${supertonicReady?'':'disabled'}>Supertonic (local)${supertonicReady?'':' — not ready'}</option>
            <option value="edge" ${ttsProvider==='edge'?'selected':''}>Edge (cloud, free)</option>
            <option value="elevenlabs" ${ttsProvider==='elevenlabs'?'selected':''}>ElevenLabs (cloud, paid)</option>
            <option value="unset" ${ttsProvider==='auto'?'selected':''}>auto (let the bot decide)</option>
          </select>
          <button class="btn" onclick="applyTtsProvider()">${t('reliability.tts.apply')}</button>
        </div>
        <div class="sub" style="font-size:0.78em;margin-top:6px">${t('reliability.tts.help')}</div>
      </div>

      <div class="card" style="grid-column:1/-1">
        <h3 style="font-size:0.9em;text-transform:none;letter-spacing:0;margin-bottom:8px;display:flex;align-items:center;gap:8px">
          ${icon('alert-triangle', 16)} ${t('reliability.incidents.title')}
        </h3>
        <table style="width:100%;font-size:0.85em;border-collapse:collapse">
          <thead><tr style="text-align:left;color:var(--fg3);font-size:0.78em">
            <th style="padding:4px 0">${t('reliability.incidents.when')}</th>
            <th style="padding:4px 0">${t('reliability.incidents.kind')}</th>
          </tr></thead>
          <tbody>${incidentRows}</tbody>
        </table>
        <div class="sub" style="font-size:0.78em;margin-top:8px">${t('reliability.incidents.help')}</div>
      </div>
    </div>
  `;
}

async function applyTtsProvider() {
  const sel = document.getElementById('tts-provider-select');
  if (!sel) return;
  const provider = sel.value;
  try {
    const r = await fetch(API + '/api/tts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ provider }),
    });
    const data = await r.json();
    if (data.ok) {
      toast(`${t('reliability.tts.applied')}: ${provider}. ${t('reliability.tts.restarting')}`, 'success');
      setTimeout(() => renderReliability(), 6000);
    } else {
      toast(data.error || 'Switch failed', 'error');
    }
  } catch (err) {
    toast(err.message || String(err), 'error');
  }
}

// ── Init ────────────────────────────────────────────────
initUI();
initDragDrop();
restoreChatFromStorage();
if (chatMessages.length > 0) scrollToBottom();
connectWS();

// Check if first-run setup is needed
checkSetupNeeded().then(needed => {
  if (!needed) { loadDashboard(); loadModels(); }
});